Merge branch 'v0.6.1' into docker-env

This commit is contained in:
Paul Hauner 2019-06-04 09:25:00 +10:00
commit 39cb63e01e
No known key found for this signature in database
GPG Key ID: 303E4494BB28068C
149 changed files with 5350 additions and 3060 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "tests/ef_tests/eth2.0-spec-tests"]
path = tests/ef_tests/eth2.0-spec-tests
url = https://github.com/ethereum/eth2.0-spec-tests

View File

@ -7,6 +7,8 @@ members = [
"eth2/utils/bls",
"eth2/utils/boolean-bitfield",
"eth2/utils/cached_tree_hash",
"eth2/utils/compare_fields",
"eth2/utils/compare_fields_derive",
"eth2/utils/fixed_len_vec",
"eth2/utils/hashing",
"eth2/utils/honey-badger-split",
@ -30,6 +32,7 @@ members = [
"beacon_node/rpc",
"beacon_node/version",
"beacon_node/beacon_chain",
"tests/ef_tests",
"protos",
"validator_client",
"account_manager",

View File

@ -18,6 +18,7 @@ use state_processing::{
};
use std::sync::Arc;
use store::{Error as DBError, Store};
use tree_hash::TreeHash;
use types::*;
#[derive(Debug, PartialEq)]
@ -380,8 +381,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// If required, transition the new state to the present slot.
for _ in state.slot.as_u64()..present_slot.as_u64() {
// Ensure the next epoch state caches are built in case of an epoch transition.
state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec)?;
state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?;
state.build_committee_cache(RelativeEpoch::Next, spec)?;
per_slot_processing(&mut *state, spec)?;
}
@ -463,7 +463,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn block_proposer(&self, slot: Slot) -> Result<usize, BeaconStateError> {
self.state
.write()
.build_epoch_cache(RelativeEpoch::Current, &T::EthSpec::spec())?;
.build_committee_cache(RelativeEpoch::Current, &T::EthSpec::spec())?;
let index = self.state.read().get_beacon_proposer_index(
slot,
@ -489,7 +489,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
if let Some(attestation_duty) = self
.state
.read()
.get_attestation_duties(validator_index, &T::EthSpec::spec())?
.get_attestation_duties(validator_index, RelativeEpoch::Current)?
{
Ok(Some((attestation_duty.slot, attestation_duty.shard)))
} else {
@ -531,18 +531,21 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
*self.state.read().get_block_root(current_epoch_start_slot)?
};
let previous_crosslink_root =
Hash256::from_slice(&state.get_current_crosslink(shard)?.tree_hash_root());
self.metrics.attestation_production_successes.inc();
timer.observe_duration();
Ok(AttestationData {
slot: self.state.read().slot,
shard,
beacon_block_root: self.head().beacon_block_root,
target_root,
crosslink_data_root: Hash256::zero(),
previous_crosslink: state.latest_crosslinks[shard as usize].clone(),
source_epoch: state.current_justified_epoch,
source_root: state.current_justified_root,
target_epoch: state.current_epoch(),
target_root,
shard,
previous_crosslink_root,
crosslink_data_root: Hash256::zero(),
})
}
@ -701,7 +704,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.fork_choice()?;
self.metrics.block_processing_successes.inc();
self.metrics.operations_per_block_attestation.observe(block.body.attestations.len() as f64);
self.metrics
.operations_per_block_attestation
.observe(block.body.attestations.len() as f64);
timer.observe_duration();
Ok(BlockProcessingOutcome::ValidBlock(ValidBlock::Processed))
@ -742,9 +747,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
randao_reveal,
eth1_data: Eth1Data {
// TODO: replace with real data
deposit_count: 0,
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
},
// TODO: badass Lighthouse graffiti
graffiti: [0; 32],
proposer_slashings,
attester_slashings,
attestations: self
@ -793,7 +801,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let justified_root = {
let root = self.head().beacon_state.current_justified_root;
if root == T::EthSpec::spec().zero_hash {
self.genesis_block_root
self.genesis_block_root
} else {
root
}

View File

@ -115,7 +115,9 @@ impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
};
// get attestation duties and check if validator is active
let attestation_duties = match state.get_attestation_duties(val_index, &spec) {
let attestation_duties = match state
.get_attestation_duties(val_index, RelativeEpoch::Current)
{
Ok(Some(v)) => v,
Ok(_) => {
// validator is inactive, go to the next validator

View File

@ -71,10 +71,8 @@ impl<T: Store, E: EthSpec> BitwiseLMDGhost<T, E> {
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
for index in active_validator_indices {
let balance = std::cmp::min(
current_state.validator_balances[index],
spec.max_deposit_amount,
) / spec.fork_choice_balance_increment;
let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance)
/ spec.effective_balance_increment;
if balance > 0 {
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;

View File

@ -69,11 +69,10 @@ impl<T: Store, E: EthSpec> OptimizedLMDGhost<T, E> {
let active_validator_indices =
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
let validator_balances = &current_state.validator_balances;
for index in active_validator_indices {
let balance = std::cmp::min(validator_balances[index], spec.max_deposit_amount)
/ spec.fork_choice_balance_increment;
let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance)
/ spec.effective_balance_increment;
if balance > 0 {
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;

View File

@ -43,10 +43,8 @@ impl<T: Store, E: EthSpec> SlowLMDGhost<T, E> {
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
for index in active_validator_indices {
let balance = std::cmp::min(
current_state.validator_balances[index],
spec.max_deposit_amount,
) / spec.fork_choice_balance_increment;
let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance)
/ spec.effective_balance_increment;
if balance > 0 {
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;

View File

@ -64,6 +64,7 @@ fn test_yaml_vectors<T: ForkChoice<MemoryStore>>(
let spec = FoundationEthSpec::spec();
let zero_hash = Hash256::zero();
let eth1_data = Eth1Data {
deposit_count: 0,
deposit_root: zero_hash.clone(),
block_hash: zero_hash.clone(),
};
@ -72,6 +73,7 @@ fn test_yaml_vectors<T: ForkChoice<MemoryStore>>(
let body = BeaconBlockBody {
eth1_data,
randao_reveal,
graffiti: [0; 32],
proposer_slashings: vec![],
attester_slashings: vec![],
attestations: vec![],

View File

@ -6,10 +6,12 @@ use state_processing::per_block_processing::errors::{
AttestationValidationError, AttesterSlashingValidationError, DepositValidationError,
ExitValidationError, ProposerSlashingValidationError, TransferValidationError,
};
#[cfg(not(test))]
use state_processing::per_block_processing::verify_deposit_merkle_proof;
use state_processing::per_block_processing::{
gather_attester_slashing_indices_modular, validate_attestation,
validate_attestation_time_independent_only, verify_attester_slashing, verify_deposit,
verify_exit, verify_exit_time_independent_only, verify_proposer_slashing, verify_transfer,
get_slashable_indices_modular, validate_attestation,
validate_attestation_time_independent_only, verify_attester_slashing, verify_exit,
verify_exit_time_independent_only, verify_proposer_slashing, verify_transfer,
verify_transfer_time_independent_only,
};
use std::collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet};
@ -20,11 +22,6 @@ use types::{
EthSpec, ProposerSlashing, Transfer, Validator, VoluntaryExit,
};
#[cfg(test)]
const VERIFY_DEPOSIT_PROOFS: bool = false;
#[cfg(not(test))]
const VERIFY_DEPOSIT_PROOFS: bool = false; // TODO: enable this
#[derive(Default)]
pub struct OperationPool<T: EthSpec + Default> {
/// Map from attestation ID (see below) to vectors of attestations.
@ -60,7 +57,7 @@ impl AttestationId {
spec: &ChainSpec,
) -> Self {
let mut bytes = ssz_encode(attestation);
let epoch = attestation.slot.epoch(spec.slots_per_epoch);
let epoch = attestation.target_epoch;
bytes.extend_from_slice(&AttestationId::compute_domain_bytes(epoch, state, spec));
AttestationId(bytes)
}
@ -85,19 +82,13 @@ impl AttestationId {
/// receive for including it in a block.
// TODO: this could be optimised with a map from validator index to whether that validator has
// attested in each of the current and previous epochs. Currently quadractic in number of validators.
fn attestation_score<T: EthSpec>(
attestation: &Attestation,
state: &BeaconState<T>,
spec: &ChainSpec,
) -> usize {
fn attestation_score<T: EthSpec>(attestation: &Attestation, state: &BeaconState<T>) -> usize {
// Bitfield of validators whose attestations are new/fresh.
let mut new_validators = attestation.aggregation_bitfield.clone();
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
let state_attestations = if attestation_epoch == state.current_epoch(spec) {
let state_attestations = if attestation.data.target_epoch == state.current_epoch() {
&state.current_epoch_attestations
} else if attestation_epoch == state.previous_epoch(spec) {
} else if attestation.data.target_epoch == state.previous_epoch() {
&state.previous_epoch_attestations
} else {
return 0;
@ -181,8 +172,8 @@ impl<T: EthSpec> OperationPool<T> {
/// Get a list of attestations for inclusion in a block.
pub fn get_attestations(&self, state: &BeaconState<T>, spec: &ChainSpec) -> Vec<Attestation> {
// Attestations for the current fork, which may be from the current or previous epoch.
let prev_epoch = state.previous_epoch(spec);
let current_epoch = state.current_epoch(spec);
let prev_epoch = state.previous_epoch();
let current_epoch = state.current_epoch();
let prev_domain_bytes = AttestationId::compute_domain_bytes(prev_epoch, state, spec);
let curr_domain_bytes = AttestationId::compute_domain_bytes(current_epoch, state, spec);
self.attestations
@ -199,7 +190,7 @@ impl<T: EthSpec> OperationPool<T> {
.filter(|attestation| validate_attestation(state, attestation, spec).is_ok())
// Scored by the number of new attestations they introduce (descending)
// TODO: need to consider attestations introduced in THIS block
.map(|att| (att, attestation_score(att, state, spec)))
.map(|att| (att, attestation_score(att, state)))
// Don't include any useless attestations (score 0)
.filter(|&(_, score)| score != 0)
.sorted_by_key(|&(_, score)| std::cmp::Reverse(score))
@ -211,15 +202,16 @@ impl<T: EthSpec> OperationPool<T> {
}
/// Remove attestations which are too old to be included in a block.
// TODO: we could probably prune other attestations here:
// - ones that are completely covered by attestations included in the state
// - maybe ones invalidated by the confirmation of one fork over another
pub fn prune_attestations(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
pub fn prune_attestations(&self, finalized_state: &BeaconState<T>) {
// We know we can include an attestation if:
// state.slot <= attestation_slot + SLOTS_PER_EPOCH
// We approximate this check using the attestation's epoch, to avoid computing
// the slot or relying on the committee cache of the finalized state.
self.attestations.write().retain(|_, attestations| {
// All the attestations in this bucket have the same data, so we only need to
// check the first one.
attestations.first().map_or(false, |att| {
finalized_state.slot < att.data.slot + spec.slots_per_epoch
finalized_state.current_epoch() <= att.data.target_epoch + 1
})
});
}
@ -227,6 +219,7 @@ impl<T: EthSpec> OperationPool<T> {
/// Add a deposit to the pool.
///
/// No two distinct deposits should be added with the same index.
#[cfg_attr(test, allow(unused_variables))]
pub fn insert_deposit(
&self,
deposit: Deposit,
@ -237,7 +230,9 @@ impl<T: EthSpec> OperationPool<T> {
match self.deposits.write().entry(deposit.index) {
Entry::Vacant(entry) => {
verify_deposit(state, &deposit, VERIFY_DEPOSIT_PROOFS, spec)?;
// TODO: fix tests to generate valid merkle proofs
#[cfg(not(test))]
verify_deposit_merkle_proof(state, &deposit, spec)?;
entry.insert(deposit);
Ok(Fresh)
}
@ -245,7 +240,9 @@ impl<T: EthSpec> OperationPool<T> {
if entry.get() == &deposit {
Ok(Duplicate)
} else {
verify_deposit(state, &deposit, VERIFY_DEPOSIT_PROOFS, spec)?;
// TODO: fix tests to generate valid merkle proofs
#[cfg(not(test))]
verify_deposit_merkle_proof(state, &deposit, spec)?;
Ok(Replaced(Box::new(entry.insert(deposit))))
}
}
@ -256,6 +253,7 @@ impl<T: EthSpec> OperationPool<T> {
///
/// Take at most the maximum number of deposits, beginning from the current deposit index.
pub fn get_deposits(&self, state: &BeaconState<T>, spec: &ChainSpec) -> Vec<Deposit> {
// TODO: might want to re-check the Merkle proof to account for Eth1 forking
let start_idx = state.deposit_index;
(start_idx..start_idx + spec.max_deposits)
.map(|idx| self.deposits.read().get(&idx).cloned())
@ -300,8 +298,8 @@ impl<T: EthSpec> OperationPool<T> {
spec: &ChainSpec,
) -> (AttestationId, AttestationId) {
(
AttestationId::from_data(&slashing.slashable_attestation_1.data, state, spec),
AttestationId::from_data(&slashing.slashable_attestation_2.data, state, spec),
AttestationId::from_data(&slashing.attestation_1.data, state, spec),
AttestationId::from_data(&slashing.attestation_2.data, state, spec),
)
}
@ -356,12 +354,10 @@ impl<T: EthSpec> OperationPool<T> {
})
.filter(|(_, slashing)| {
// Take all slashings that will slash 1 or more validators.
let slashed_validators = gather_attester_slashing_indices_modular(
state,
slashing,
|index, validator| validator.slashed || to_be_slashed.contains(&index),
spec,
);
let slashed_validators =
get_slashable_indices_modular(state, slashing, |index, validator| {
validator.slashed || to_be_slashed.contains(&index)
});
// Extend the `to_be_slashed` set so subsequent iterations don't try to include
// useless slashings.
@ -380,12 +376,11 @@ impl<T: EthSpec> OperationPool<T> {
}
/// Prune proposer slashings for all slashed or withdrawn validators.
pub fn prune_proposer_slashings(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
pub fn prune_proposer_slashings(&self, finalized_state: &BeaconState<T>) {
prune_validator_hash_map(
&mut self.proposer_slashings.write(),
|validator| {
validator.slashed
|| validator.is_withdrawable_at(finalized_state.current_epoch(spec))
validator.slashed || validator.is_withdrawable_at(finalized_state.current_epoch())
},
finalized_state,
);
@ -396,14 +391,12 @@ impl<T: EthSpec> OperationPool<T> {
pub fn prune_attester_slashings(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
self.attester_slashings.write().retain(|id, slashing| {
let fork_ok = &Self::attester_slashing_id(slashing, finalized_state, spec) == id;
let curr_epoch = finalized_state.current_epoch(spec);
let slashing_ok = gather_attester_slashing_indices_modular(
finalized_state,
slashing,
|_, validator| validator.slashed || validator.is_withdrawable_at(curr_epoch),
spec,
)
.is_ok();
let curr_epoch = finalized_state.current_epoch();
let slashing_ok =
get_slashable_indices_modular(finalized_state, slashing, |_, validator| {
validator.slashed || validator.is_withdrawable_at(curr_epoch)
})
.is_ok();
fork_ok && slashing_ok
});
}
@ -436,10 +429,10 @@ impl<T: EthSpec> OperationPool<T> {
}
/// Prune if validator has already exited at the last finalized state.
pub fn prune_voluntary_exits(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
pub fn prune_voluntary_exits(&self, finalized_state: &BeaconState<T>) {
prune_validator_hash_map(
&mut self.voluntary_exits.write(),
|validator| validator.is_exited_at(finalized_state.current_epoch(spec)),
|validator| validator.is_exited_at(finalized_state.current_epoch()),
finalized_state,
);
}
@ -482,11 +475,11 @@ impl<T: EthSpec> OperationPool<T> {
/// Prune all types of transactions given the latest finalized state.
pub fn prune_all(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
self.prune_attestations(finalized_state, spec);
self.prune_attestations(finalized_state);
self.prune_deposits(finalized_state);
self.prune_proposer_slashings(finalized_state, spec);
self.prune_proposer_slashings(finalized_state);
self.prune_attester_slashings(finalized_state, spec);
self.prune_voluntary_exits(finalized_state, spec);
self.prune_voluntary_exits(finalized_state);
self.prune_transfers(finalized_state);
}
}
@ -566,8 +559,8 @@ mod tests {
let rng = &mut XorShiftRng::from_seed([42; 16]);
let (ref spec, ref state) = test_state(rng);
let op_pool = OperationPool::new();
let deposit1 = make_deposit(rng, state, spec);
let mut deposit2 = make_deposit(rng, state, spec);
let deposit1 = make_deposit(rng);
let mut deposit2 = make_deposit(rng);
deposit2.index = deposit1.index;
assert_eq!(
@ -595,7 +588,7 @@ mod tests {
let offset = 1;
assert!(offset <= extra);
let deposits = dummy_deposits(rng, &state, &spec, start, max_deposits + extra);
let deposits = dummy_deposits(rng, start, max_deposits + extra);
for deposit in &deposits {
assert_eq!(
@ -626,8 +619,8 @@ mod tests {
let gap = 25;
let start2 = start1 + count + gap;
let deposits1 = dummy_deposits(rng, &state, &spec, start1, count);
let deposits2 = dummy_deposits(rng, &state, &spec, start2, count);
let deposits1 = dummy_deposits(rng, start1, count);
let deposits2 = dummy_deposits(rng, start2, count);
for d in deposits1.into_iter().chain(deposits2) {
assert!(op_pool.insert_deposit(d, &state, &spec).is_ok());
@ -665,38 +658,14 @@ mod tests {
assert_eq!(op_pool.num_deposits(), 0);
}
// Create a random deposit (with a valid proof of posession)
fn make_deposit<T: EthSpec>(
rng: &mut XorShiftRng,
state: &BeaconState<T>,
spec: &ChainSpec,
) -> Deposit {
let keypair = Keypair::random();
let mut deposit = Deposit::random_for_test(rng);
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,
state.slot.epoch(spec.slots_per_epoch),
&state.fork,
spec,
);
deposit.deposit_data.deposit_input = deposit_input;
deposit
// Create a random deposit
fn make_deposit(rng: &mut XorShiftRng) -> Deposit {
Deposit::random_for_test(rng)
}
// Create `count` dummy deposits with sequential deposit IDs beginning from `start`.
fn dummy_deposits<T: EthSpec>(
rng: &mut XorShiftRng,
state: &BeaconState<T>,
spec: &ChainSpec,
start: u64,
count: u64,
) -> Vec<Deposit> {
let proto_deposit = make_deposit(rng, state, spec);
fn dummy_deposits(rng: &mut XorShiftRng, start: u64, count: u64) -> Vec<Deposit> {
let proto_deposit = make_deposit(rng);
(start..start + count)
.map(|index| {
let mut deposit = proto_deposit.clone();
@ -723,7 +692,8 @@ mod tests {
/// Create a signed attestation for use in tests.
/// Signed by all validators in `committee[signing_range]` and `committee[extra_signer]`.
fn signed_attestation<R: std::slice::SliceIndex<[usize], Output = [usize]>, E: EthSpec>(
committee: &CrosslinkCommittee,
committee: &[usize],
shard: u64,
keypairs: &[Keypair],
signing_range: R,
slot: Slot,
@ -731,18 +701,12 @@ mod tests {
spec: &ChainSpec,
extra_signer: Option<usize>,
) -> Attestation {
let mut builder = TestingAttestationBuilder::new(
state,
&committee.committee,
slot,
committee.shard,
spec,
);
let signers = &committee.committee[signing_range];
let mut builder = TestingAttestationBuilder::new(state, committee, slot, shard, spec);
let signers = &committee[signing_range];
let committee_keys = signers.iter().map(|&i| &keypairs[i].sk).collect::<Vec<_>>();
builder.sign(signers, &committee_keys, &state.fork, spec);
extra_signer.map(|c_idx| {
let validator_index = committee.committee[c_idx];
let validator_index = committee[c_idx];
builder.sign(
&[validator_index],
&[&keypairs[validator_index].sk],
@ -760,7 +724,7 @@ mod tests {
let spec = E::spec();
let num_validators =
num_committees * (spec.slots_per_epoch * spec.target_committee_size) as usize;
num_committees * spec.slots_per_epoch as usize * spec.target_committee_size;
let mut state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(
num_validators,
&spec,
@ -774,18 +738,6 @@ mod tests {
(state, keypairs, FoundationEthSpec::spec())
}
/// Set the latest crosslink in the state to match the attestation.
fn fake_latest_crosslink<E: EthSpec>(
att: &Attestation,
state: &mut BeaconState<E>,
spec: &ChainSpec,
) {
state.latest_crosslinks[att.data.shard as usize] = Crosslink {
crosslink_data_root: att.data.crosslink_data_root,
epoch: att.data.slot.epoch(spec.slots_per_epoch),
};
}
#[test]
fn test_attestation_score() {
let (ref mut state, ref keypairs, ref spec) =
@ -793,27 +745,47 @@ mod tests {
let slot = state.slot - 1;
let committees = state
.get_crosslink_committees_at_slot(slot, spec)
.get_crosslink_committees_at_slot(slot)
.unwrap()
.clone();
.into_iter()
.map(CrosslinkCommittee::into_owned)
.collect::<Vec<_>>();
for committee in committees {
let att1 = signed_attestation(&committee, keypairs, ..2, slot, state, spec, None);
let att2 = signed_attestation(&committee, keypairs, .., slot, state, spec, None);
for cc in committees {
let att1 = signed_attestation(
&cc.committee,
cc.shard,
keypairs,
..2,
slot,
state,
spec,
None,
);
let att2 = signed_attestation(
&cc.committee,
cc.shard,
keypairs,
..,
slot,
state,
spec,
None,
);
assert_eq!(
att1.aggregation_bitfield.num_set_bits(),
attestation_score(&att1, state, spec)
attestation_score(&att1, state)
);
state
.current_epoch_attestations
.push(PendingAttestation::from_attestation(&att1, state.slot));
state.current_epoch_attestations.push(PendingAttestation {
aggregation_bitfield: att1.aggregation_bitfield.clone(),
data: att1.data.clone(),
inclusion_delay: 0,
proposer_index: 0,
});
assert_eq!(
committee.committee.len() - 2,
attestation_score(&att2, state, spec)
);
assert_eq!(cc.committee.len() - 2, attestation_score(&att2, state));
}
}
@ -827,9 +799,11 @@ mod tests {
let slot = state.slot - 1;
let committees = state
.get_crosslink_committees_at_slot(slot, spec)
.get_crosslink_committees_at_slot(slot)
.unwrap()
.clone();
.into_iter()
.map(CrosslinkCommittee::into_owned)
.collect::<Vec<_>>();
assert_eq!(
committees.len(),
@ -837,11 +811,12 @@ mod tests {
"we expect just one committee with this many validators"
);
for committee in &committees {
for cc in &committees {
let step_size = 2;
for i in (0..committee.committee.len()).step_by(step_size) {
for i in (0..cc.committee.len()).step_by(step_size) {
let att = signed_attestation(
committee,
&cc.committee,
cc.shard,
keypairs,
i..i + step_size,
slot,
@ -849,7 +824,6 @@ mod tests {
spec,
None,
);
fake_latest_crosslink(&att, state, spec);
op_pool.insert_attestation(att, state, spec).unwrap();
}
}
@ -873,13 +847,13 @@ mod tests {
);
// Prune attestations shouldn't do anything at this point.
op_pool.prune_attestations(state, spec);
op_pool.prune_attestations(state);
assert_eq!(op_pool.num_attestations(), committees.len());
// But once we advance to an epoch after the attestation, it should prune it out of
// existence.
state.slot = slot + spec.slots_per_epoch;
op_pool.prune_attestations(state, spec);
// But once we advance to more than an epoch after the attestation, it should prune it
// out of existence.
state.slot += 2 * spec.slots_per_epoch;
op_pool.prune_attestations(state);
assert_eq!(op_pool.num_attestations(), 0);
}
@ -893,13 +867,23 @@ mod tests {
let slot = state.slot - 1;
let committees = state
.get_crosslink_committees_at_slot(slot, spec)
.get_crosslink_committees_at_slot(slot)
.unwrap()
.clone();
.into_iter()
.map(CrosslinkCommittee::into_owned)
.collect::<Vec<_>>();
for committee in &committees {
let att = signed_attestation(committee, keypairs, .., slot, state, spec, None);
fake_latest_crosslink(&att, state, spec);
for cc in &committees {
let att = signed_attestation(
&cc.committee,
cc.shard,
keypairs,
..,
slot,
state,
spec,
None,
);
op_pool
.insert_attestation(att.clone(), state, spec)
.unwrap();
@ -920,17 +904,20 @@ mod tests {
let slot = state.slot - 1;
let committees = state
.get_crosslink_committees_at_slot(slot, spec)
.get_crosslink_committees_at_slot(slot)
.unwrap()
.clone();
.into_iter()
.map(CrosslinkCommittee::into_owned)
.collect::<Vec<_>>();
let step_size = 2;
for committee in &committees {
for cc in &committees {
// Create attestations that overlap on `step_size` validators, like:
// {0,1,2,3}, {2,3,4,5}, {4,5,6,7}, ...
for i in (0..committee.committee.len() - step_size).step_by(step_size) {
for i in (0..cc.committee.len() - step_size).step_by(step_size) {
let att = signed_attestation(
committee,
&cc.committee,
cc.shard,
keypairs,
i..i + 2 * step_size,
slot,
@ -938,7 +925,6 @@ mod tests {
spec,
None,
);
fake_latest_crosslink(&att, state, spec);
op_pool.insert_attestation(att, state, spec).unwrap();
}
}
@ -966,17 +952,20 @@ mod tests {
let slot = state.slot - 1;
let committees = state
.get_crosslink_committees_at_slot(slot, spec)
.get_crosslink_committees_at_slot(slot)
.unwrap()
.clone();
.into_iter()
.map(CrosslinkCommittee::into_owned)
.collect::<Vec<_>>();
let max_attestations = spec.max_attestations as usize;
let target_committee_size = spec.target_committee_size as usize;
let mut insert_attestations = |committee, step_size| {
let insert_attestations = |cc: &OwnedCrosslinkCommittee, step_size| {
for i in (0..target_committee_size).step_by(step_size) {
let att = signed_attestation(
committee,
&cc.committee,
cc.shard,
keypairs,
i..i + step_size,
slot,
@ -984,7 +973,6 @@ mod tests {
spec,
if i == 0 { None } else { Some(0) },
);
fake_latest_crosslink(&att, state, spec);
op_pool.insert_attestation(att, state, spec).unwrap();
}
};

View File

@ -21,6 +21,7 @@ fnv = "1.0"
hashing = { path = "../utils/hashing" }
int_to_bytes = { path = "../utils/int_to_bytes" }
integer-sqrt = "0.1"
itertools = "0.8"
log = "0.4"
merkle_proof = { path = "../utils/merkle_proof" }
ssz = { path = "../utils/ssz" }

View File

@ -207,12 +207,12 @@ pub fn bench_block_processing(
let spec = initial_spec.clone();
c.bench(
&format!("{}/block_processing", desc),
Benchmark::new("build_previous_state_epoch_cache", move |b| {
Benchmark::new("build_previous_state_committee_cache", move |b| {
b.iter_batched(
|| state.clone(),
|mut state| {
state
.build_epoch_cache(RelativeEpoch::Previous, &spec)
.build_committee_cache(RelativeEpoch::Previous, &spec)
.unwrap();
state
},
@ -227,12 +227,12 @@ pub fn bench_block_processing(
let spec = initial_spec.clone();
c.bench(
&format!("{}/block_processing", desc),
Benchmark::new("build_current_state_epoch_cache", move |b| {
Benchmark::new("build_current_state_committee_cache", move |b| {
b.iter_batched(
|| state.clone(),
|mut state| {
state
.build_epoch_cache(RelativeEpoch::Current, &spec)
.build_committee_cache(RelativeEpoch::Current, &spec)
.unwrap();
state
},

View File

@ -0,0 +1,29 @@
use super::get_attesting_indices;
use itertools::{Either, Itertools};
use types::*;
/// Convert `attestation` to (almost) indexed-verifiable form.
///
/// Spec v0.6.1
pub fn convert_to_indexed<T: EthSpec>(
state: &BeaconState<T>,
attestation: &Attestation,
) -> Result<IndexedAttestation, BeaconStateError> {
let attesting_indices =
get_attesting_indices(state, &attestation.data, &attestation.aggregation_bitfield)?;
let (custody_bit_0_indices, custody_bit_1_indices) =
attesting_indices.into_iter().enumerate().partition_map(
|(committee_idx, validator_idx)| match attestation.custody_bitfield.get(committee_idx) {
Ok(true) => Either::Right(validator_idx as u64),
_ => Either::Left(validator_idx as u64),
},
);
Ok(IndexedAttestation {
custody_bit_0_indices,
custody_bit_1_indices,
data: attestation.data.clone(),
signature: attestation.signature.clone(),
})
}

View File

@ -1,22 +0,0 @@
use types::{BeaconStateError as Error, *};
/// Exit the validator of the given `index`.
///
/// Spec v0.5.1
pub fn exit_validator<T: EthSpec>(
state: &mut BeaconState<T>,
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(())
}

View File

@ -0,0 +1,46 @@
use crate::common::verify_bitfield_length;
use types::*;
/// Returns validator indices which participated in the attestation, sorted by increasing index.
///
/// Spec v0.6.1
pub fn get_attesting_indices<T: EthSpec>(
state: &BeaconState<T>,
attestation_data: &AttestationData,
bitfield: &Bitfield,
) -> Result<Vec<usize>, BeaconStateError> {
get_attesting_indices_unsorted(state, attestation_data, bitfield).map(|mut indices| {
// Fast unstable sort is safe because validator indices are unique
indices.sort_unstable();
indices
})
}
/// Returns validator indices which participated in the attestation, unsorted.
///
/// Spec v0.6.1
pub fn get_attesting_indices_unsorted<T: EthSpec>(
state: &BeaconState<T>,
attestation_data: &AttestationData,
bitfield: &Bitfield,
) -> Result<Vec<usize>, BeaconStateError> {
let target_relative_epoch =
RelativeEpoch::from_epoch(state.current_epoch(), attestation_data.target_epoch)?;
let committee =
state.get_crosslink_committee_for_shard(attestation_data.shard, target_relative_epoch)?;
if !verify_bitfield_length(&bitfield, committee.committee.len()) {
return Err(BeaconStateError::InvalidBitfield);
}
Ok(committee
.committee
.iter()
.enumerate()
.filter_map(|(i, validator_index)| match bitfield.get(i) {
Ok(true) => Some(*validator_index),
_ => None,
})
.collect())
}

View File

@ -0,0 +1,39 @@
use std::cmp::max;
use types::{BeaconStateError as Error, *};
/// Initiate the exit of the validator of the given `index`.
///
/// Spec v0.6.1
pub fn initiate_validator_exit<T: EthSpec>(
state: &mut BeaconState<T>,
index: usize,
spec: &ChainSpec,
) -> Result<(), Error> {
if index >= state.validator_registry.len() {
return Err(Error::UnknownValidator);
}
// Return if the validator already initiated exit
if state.validator_registry[index].exit_epoch != spec.far_future_epoch {
return Ok(());
}
// Compute exit queue epoch
let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(), spec);
let mut exit_queue_epoch = state
.exit_cache
.max_epoch()
.map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch));
let exit_queue_churn = state.exit_cache.get_churn_at(exit_queue_epoch);
if exit_queue_churn >= state.get_churn_limit(spec)? {
exit_queue_epoch += 1;
}
state.exit_cache.record_validator_exit(exit_queue_epoch);
state.validator_registry[index].exit_epoch = exit_queue_epoch;
state.validator_registry[index].withdrawable_epoch =
exit_queue_epoch + spec.min_validator_withdrawability_delay;
Ok(())
}

View File

@ -1,7 +1,11 @@
mod exit_validator;
mod convert_to_indexed;
mod get_attesting_indices;
mod initiate_validator_exit;
mod slash_validator;
mod verify_bitfield;
pub use exit_validator::exit_validator;
pub use convert_to_indexed::convert_to_indexed;
pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_unsorted};
pub use initiate_validator_exit::initiate_validator_exit;
pub use slash_validator::slash_validator;
pub use verify_bitfield::verify_bitfield_length;

View File

@ -1,61 +1,45 @@
use crate::common::exit_validator;
use crate::common::initiate_validator_exit;
use types::{BeaconStateError as Error, *};
/// Slash the validator with index ``index``.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn slash_validator<T: EthSpec>(
state: &mut BeaconState<T>,
validator_index: usize,
slashed_index: usize,
opt_whistleblower_index: Option<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())
{
if slashed_index >= state.validator_registry.len() || slashed_index >= state.balances.len() {
return Err(BeaconStateError::UnknownValidator);
}
let validator = &state.validator_registry[validator_index];
let current_epoch = state.current_epoch();
let effective_balance = state.get_effective_balance(validator_index, spec)?;
initiate_validator_exit(state, slashed_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.validator_registry[slashed_index].slashed = true;
state.validator_registry[slashed_index].withdrawable_epoch =
current_epoch + Epoch::from(T::latest_slashed_exit_length());
let slashed_balance = state.get_effective_balance(slashed_index, spec)?;
state.set_slashed_balance(
current_epoch,
state.get_slashed_balance(current_epoch)? + effective_balance,
state.get_slashed_balance(current_epoch)? + slashed_balance,
)?;
let whistleblower_index =
let proposer_index =
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient;
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
let whistleblowing_reward = slashed_balance / spec.whistleblowing_reward_quotient;
let proposer_reward = whistleblowing_reward / spec.proposer_reward_quotient;
safe_add_assign!(state.balances[proposer_index], proposer_reward);
safe_add_assign!(
state.validator_balances[whistleblower_index as usize],
whistleblower_reward
state.balances[whistleblower_index],
whistleblowing_reward.saturating_sub(proposer_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(T::LatestSlashedExitLength::to_usize());
safe_sub_assign!(state.balances[slashed_index], whistleblowing_reward);
Ok(())
}

View File

@ -4,7 +4,7 @@ use types::*;
///
/// Is title `verify_bitfield` in spec.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn verify_bitfield_length(bitfield: &Bitfield, committee_size: usize) -> bool {
if bitfield.num_bytes() != ((committee_size + 7) / 8) {
return false;

View File

@ -9,8 +9,8 @@ pub enum GenesisError {
/// Returns the genesis `BeaconState`
///
/// Spec v0.5.1
pub fn get_genesis_state<T: EthSpec>(
/// Spec v0.6.1
pub fn get_genesis_beacon_state<T: EthSpec>(
genesis_validator_deposits: &[Deposit],
genesis_time: u64,
genesis_eth1_data: Eth1Data,
@ -23,25 +23,23 @@ pub fn get_genesis_state<T: EthSpec>(
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;
for validator in &mut state.validator_registry {
if validator.effective_balance >= spec.max_effective_balance {
validator.activation_eligibility_epoch = spec.genesis_epoch;
validator.activation_epoch = spec.genesis_epoch;
}
}
// Ensure the current epoch cache is built.
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
state.build_committee_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)?
.get_cached_active_validator_indices(RelativeEpoch::Current)?
.to_vec();
let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.tree_hash_root());
state.fill_active_index_roots_with(genesis_active_index_root);
// Generate the current shuffling seed.
state.current_shuffling_seed = state.generate_seed(spec.genesis_epoch, spec)?;
Ok(state)
}

View File

@ -7,7 +7,7 @@ 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 get_genesis_state::get_genesis_beacon_state;
pub use per_block_processing::{
errors::{BlockInvalid, BlockProcessingError},
per_block_processing, per_block_processing_without_verifying_block_signature,

View File

@ -1,21 +1,25 @@
use crate::common::slash_validator;
use crate::common::{initiate_validator_exit, slash_validator};
use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
use rayon::prelude::*;
use tree_hash::{SignedRoot, TreeHash};
use types::*;
pub use self::verify_attester_slashing::{
gather_attester_slashing_indices, gather_attester_slashing_indices_modular,
verify_attester_slashing,
get_slashable_indices, get_slashable_indices_modular, verify_attester_slashing,
};
pub use self::verify_proposer_slashing::verify_proposer_slashing;
pub use validate_attestation::{
validate_attestation, validate_attestation_time_independent_only,
validate_attestation_without_signature,
};
pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
pub use verify_deposit::{
get_existing_validator_index, verify_deposit_index, verify_deposit_merkle_proof,
verify_deposit_signature,
};
pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
pub use verify_slashable_attestation::verify_slashable_attestation;
pub use verify_indexed_attestation::{
verify_indexed_attestation, verify_indexed_attestation_without_signature,
};
pub use verify_transfer::{
execute_transfer, verify_transfer, verify_transfer_time_independent_only,
};
@ -27,21 +31,16 @@ mod validate_attestation;
mod verify_attester_slashing;
mod verify_deposit;
mod verify_exit;
mod verify_indexed_attestation;
mod verify_proposer_slashing;
mod verify_slashable_attestation;
mod verify_transfer;
// Set to `true` to check the merkle proof that a deposit is in the eth1 deposit root.
//
// Presently disabled to make testing easier.
const VERIFY_DEPOSIT_MERKLE_PROOFS: bool = false;
/// Updates the state for a new block, whilst validating that the block is valid.
///
/// 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.5.1
/// Spec v0.6.1
pub fn per_block_processing<T: EthSpec>(
state: &mut BeaconState<T>,
block: &BeaconBlock,
@ -56,7 +55,7 @@ pub fn per_block_processing<T: EthSpec>(
/// 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.5.1
/// Spec v0.6.1
pub fn per_block_processing_without_verifying_block_signature<T: EthSpec>(
state: &mut BeaconState<T>,
block: &BeaconBlock,
@ -71,7 +70,7 @@ pub fn per_block_processing_without_verifying_block_signature<T: EthSpec>(
/// 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.5.1
/// Spec v0.6.1
fn per_block_processing_signature_optional<T: EthSpec>(
mut state: &mut BeaconState<T>,
block: &BeaconBlock,
@ -80,15 +79,15 @@ fn per_block_processing_signature_optional<T: EthSpec>(
) -> Result<(), Error> {
process_block_header(state, block, spec)?;
// Ensure the current and previous epoch cache is built.
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
// Ensure the current and previous epoch caches are built.
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
state.build_committee_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.body.eth1_data)?;
process_eth1_data(&mut state, &block.body.eth1_data, spec)?;
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)?;
@ -101,7 +100,7 @@ fn per_block_processing_signature_optional<T: EthSpec>(
/// Processes the block header.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_block_header<T: EthSpec>(
state: &mut BeaconState<T>,
block: &BeaconBlock,
@ -121,12 +120,17 @@ pub fn process_block_header<T: EthSpec>(
state.latest_block_header = block.temporary_block_header(spec);
// Verify proposer is not slashed
let proposer_idx = state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?;
let proposer = &state.validator_registry[proposer_idx];
verify!(!proposer.slashed, Invalid::ProposerSlashed(proposer_idx));
Ok(())
}
/// Verifies the signature of a block.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn verify_block_signature<T: EthSpec>(
state: &BeaconState<T>,
block: &BeaconBlock,
@ -137,7 +141,7 @@ pub fn verify_block_signature<T: EthSpec>(
let domain = spec.get_domain(
block.slot.epoch(spec.slots_per_epoch),
Domain::BeaconBlock,
Domain::BeaconProposer,
&state.fork,
);
@ -154,7 +158,7 @@ pub fn verify_block_signature<T: EthSpec>(
/// Verifies the `randao_reveal` against the block's proposer pubkey and updates
/// `state.latest_randao_mixes`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_randao<T: EthSpec>(
state: &mut BeaconState<T>,
block: &BeaconBlock,
@ -166,7 +170,7 @@ pub fn process_randao<T: EthSpec>(
// Verify the RANDAO is a valid signature of the proposer.
verify!(
block.body.randao_reveal.verify(
&state.current_epoch(spec).tree_hash_root()[..],
&state.current_epoch().tree_hash_root()[..],
spec.get_domain(
block.slot.epoch(spec.slots_per_epoch),
Domain::Randao,
@ -178,32 +182,29 @@ pub fn process_randao<T: EthSpec>(
);
// Update the current epoch RANDAO mix.
state.update_randao_mix(state.current_epoch(spec), &block.body.randao_reveal, spec)?;
state.update_randao_mix(state.current_epoch(), &block.body.randao_reveal)?;
Ok(())
}
/// Update the `state.eth1_data_votes` based upon the `eth1_data` provided.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_eth1_data<T: EthSpec>(
state: &mut BeaconState<T>,
eth1_data: &Eth1Data,
spec: &ChainSpec,
) -> Result<(), Error> {
// Attempt to find a `Eth1DataVote` with matching `Eth1Data`.
let matching_eth1_vote_index = state
state.eth1_data_votes.push(eth1_data.clone());
let num_votes = state
.eth1_data_votes
.iter()
.position(|vote| vote.eth1_data == *eth1_data);
.filter(|vote| *vote == eth1_data)
.count() as u64;
// 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 {
state.eth1_data_votes.push(Eth1DataVote {
eth1_data: eth1_data.clone(),
vote_count: 1,
});
if num_votes * 2 > spec.slots_per_eth1_voting_period {
state.latest_eth1_data = eth1_data.clone();
}
Ok(())
@ -214,7 +215,7 @@ pub fn process_eth1_data<T: EthSpec>(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_proposer_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
proposer_slashings: &[ProposerSlashing],
@ -236,18 +237,18 @@ pub fn process_proposer_slashings<T: EthSpec>(
// Update the state.
for proposer_slashing in proposer_slashings {
slash_validator(state, proposer_slashing.proposer_index as usize, spec)?;
slash_validator(state, proposer_slashing.proposer_index as usize, None, spec)?;
}
Ok(())
}
/// Validates each `AttesterSlsashing` and updates the state, short-circuiting on an invalid object.
/// Validates each `AttesterSlashing` 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.5.1
/// Spec v0.6.1
pub fn process_attester_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
attester_slashings: &[AttesterSlashing],
@ -258,42 +259,42 @@ pub fn process_attester_slashings<T: EthSpec>(
Invalid::MaxAttesterSlashingsExceed
);
// Verify the `SlashableAttestation`s in parallel (these are the resource-consuming objects, not
// Verify the `IndexedAttestation`s in parallel (these are the resource-consuming objects, not
// the `AttesterSlashing`s themselves).
let mut slashable_attestations: Vec<&SlashableAttestation> =
let mut indexed_attestations: Vec<&IndexedAttestation> =
Vec::with_capacity(attester_slashings.len() * 2);
for attester_slashing in attester_slashings {
slashable_attestations.push(&attester_slashing.slashable_attestation_1);
slashable_attestations.push(&attester_slashing.slashable_attestation_2);
indexed_attestations.push(&attester_slashing.attestation_1);
indexed_attestations.push(&attester_slashing.attestation_2);
}
// Verify slashable attestations in parallel.
slashable_attestations
// Verify indexed attestations in parallel.
indexed_attestations
.par_iter()
.enumerate()
.try_for_each(|(i, slashable_attestation)| {
verify_slashable_attestation(&state, slashable_attestation, spec)
.try_for_each(|(i, indexed_attestation)| {
verify_indexed_attestation(&state, indexed_attestation, spec)
.map_err(|e| e.into_with_index(i))
})?;
let all_slashable_attestations_have_been_checked = true;
let all_indexed_attestations_have_been_checked = true;
// Gather the slashable indices and preform the final verification and update the state in series.
// Gather the indexed indices and preform the final verification and update the state in series.
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
let should_verify_slashable_attestations = !all_slashable_attestations_have_been_checked;
let should_verify_indexed_attestations = !all_indexed_attestations_have_been_checked;
verify_attester_slashing(
&state,
&attester_slashing,
should_verify_slashable_attestations,
should_verify_indexed_attestations,
spec,
)
.map_err(|e| e.into_with_index(i))?;
let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing, spec)
.map_err(|e| e.into_with_index(i))?;
let slashable_indices =
get_slashable_indices(&state, &attester_slashing).map_err(|e| e.into_with_index(i))?;
for i in slashable_indices {
slash_validator(state, i as usize, spec)?;
slash_validator(state, i as usize, None, spec)?;
}
}
@ -305,7 +306,7 @@ pub fn process_attester_slashings<T: EthSpec>(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_attestations<T: EthSpec>(
state: &mut BeaconState<T>,
attestations: &[Attestation],
@ -317,7 +318,7 @@ pub fn process_attestations<T: EthSpec>(
);
// Ensure the previous epoch cache exists.
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
// Verify attestations in parallel.
attestations
@ -328,13 +329,20 @@ pub fn process_attestations<T: EthSpec>(
})?;
// Update the state in series.
let proposer_index =
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)? as u64;
for attestation in attestations {
let pending_attestation = PendingAttestation::from_attestation(attestation, state.slot);
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
let attestation_slot = state.get_attestation_slot(&attestation.data)?;
let pending_attestation = PendingAttestation {
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
data: attestation.data.clone(),
inclusion_delay: (state.slot - attestation_slot).as_u64(),
proposer_index,
};
if attestation_epoch == state.current_epoch(spec) {
if attestation.data.target_epoch == state.current_epoch() {
state.current_epoch_attestations.push(pending_attestation)
} else if attestation_epoch == state.previous_epoch(spec) {
} else {
state.previous_epoch_attestations.push(pending_attestation)
}
}
@ -347,15 +355,19 @@ pub fn process_attestations<T: EthSpec>(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_deposits<T: EthSpec>(
state: &mut BeaconState<T>,
deposits: &[Deposit],
spec: &ChainSpec,
) -> Result<(), Error> {
verify!(
deposits.len() as u64 <= spec.max_deposits,
Invalid::MaxDepositsExceeded
deposits.len() as u64
== std::cmp::min(
spec.max_deposits,
state.latest_eth1_data.deposit_count - state.deposit_index
),
Invalid::DepositCountInvalid
);
// Verify deposits in parallel.
@ -363,50 +375,53 @@ pub fn process_deposits<T: EthSpec>(
.par_iter()
.enumerate()
.try_for_each(|(i, deposit)| {
verify_deposit(state, deposit, VERIFY_DEPOSIT_MERKLE_PROOFS, spec)
.map_err(|e| e.into_with_index(i))
verify_deposit_merkle_proof(state, deposit, spec).map_err(|e| e.into_with_index(i))
})?;
// Check `state.deposit_index` and update the state in series.
for (i, deposit) in deposits.iter().enumerate() {
verify_deposit_index(state, deposit).map_err(|e| e.into_with_index(i))?;
state.deposit_index += 1;
// Ensure the state's pubkey cache is fully up-to-date, it will be used to check to see if the
// depositing validator already exists in the registry.
state.update_pubkey_cache()?;
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
// already exists in the beacon_state.
//
// This function also verifies the withdrawal credentials.
let validator_index =
get_existing_validator_index(state, deposit).map_err(|e| e.into_with_index(i))?;
let deposit_data = &deposit.deposit_data;
let deposit_input = &deposit.deposit_data.deposit_input;
let amount = deposit.data.amount;
if let Some(index) = validator_index {
// Update the existing validator balance.
safe_add_assign!(
state.validator_balances[index as usize],
deposit_data.amount
);
safe_add_assign!(state.balances[index as usize], amount);
} else {
// The signature should be checked for new validators. Return early for a bad
// signature.
if verify_deposit_signature(state, deposit, spec).is_err() {
return Ok(());
}
// Create a new validator.
let validator = Validator {
pubkey: deposit_input.pubkey.clone(),
withdrawal_credentials: deposit_input.withdrawal_credentials,
pubkey: deposit.data.pubkey.clone(),
withdrawal_credentials: deposit.data.withdrawal_credentials,
activation_eligibility_epoch: spec.far_future_epoch,
activation_epoch: spec.far_future_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
initiated_exit: false,
effective_balance: std::cmp::min(
amount - amount % spec.effective_balance_increment,
spec.max_effective_balance,
),
slashed: false,
};
state.validator_registry.push(validator);
state.validator_balances.push(deposit_data.amount);
state.balances.push(deposit.data.amount);
}
state.deposit_index += 1;
}
Ok(())
@ -417,7 +432,7 @@ pub fn process_deposits<T: EthSpec>(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_exits<T: EthSpec>(
state: &mut BeaconState<T>,
voluntary_exits: &[VoluntaryExit],
@ -438,7 +453,7 @@ pub fn process_exits<T: EthSpec>(
// Update the state in series.
for exit in voluntary_exits {
state.initiate_validator_exit(exit.validator_index as usize);
initiate_validator_exit(state, exit.validator_index as usize, spec)?;
}
Ok(())
@ -449,7 +464,7 @@ pub fn process_exits<T: EthSpec>(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_transfers<T: EthSpec>(
state: &mut BeaconState<T>,
transfers: &[Transfer],

View File

@ -71,19 +71,20 @@ pub enum BlockInvalid {
state: Hash256,
block: Hash256,
},
ProposerSlashed(usize),
BadSignature,
BadRandaoSignature,
MaxAttestationsExceeded,
MaxAttesterSlashingsExceed,
MaxProposerSlashingsExceeded,
MaxDepositsExceeded,
DepositCountInvalid,
MaxExitsExceeded,
MaxTransfersExceed,
AttestationInvalid(usize, AttestationInvalid),
/// A `SlashableAttestation` inside an `AttesterSlashing` was invalid.
/// A `IndexedAttestation` inside an `AttesterSlashing` was invalid.
///
/// To determine the offending `AttesterSlashing` index, divide the error message `usize` by two.
SlashableAttestationInvalid(usize, SlashableAttestationInvalid),
IndexedAttestationInvalid(usize, IndexedAttestationInvalid),
AttesterSlashingInvalid(usize, AttesterSlashingInvalid),
ProposerSlashingInvalid(usize, ProposerSlashingInvalid),
DepositInvalid(usize, DepositInvalid),
@ -125,6 +126,8 @@ pub enum AttestationInvalid {
},
/// Attestation slot is too far in the past to be included in a block.
IncludedTooLate { state: Slot, attestation: Slot },
/// Attestation target epoch does not match the current or previous epoch.
BadTargetEpoch,
/// Attestation justified epoch does not match the states current or previous justified epoch.
///
/// `is_current` is `true` if the attestation was compared to the
@ -169,11 +172,20 @@ pub enum AttestationInvalid {
BadSignature,
/// The shard block root was not set to zero. This is a phase 0 requirement.
ShardBlockRootNotZero,
/// The indexed attestation created from this attestation was found to be invalid.
BadIndexedAttestation(IndexedAttestationInvalid),
}
impl_from_beacon_state_error!(AttestationValidationError);
impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
impl From<IndexedAttestationValidationError> for AttestationValidationError {
fn from(err: IndexedAttestationValidationError) -> Self {
let IndexedAttestationValidationError::Invalid(e) = err;
AttestationValidationError::Invalid(AttestationInvalid::BadIndexedAttestation(e))
}
}
/*
* `AttesterSlashing` Validation
*/
@ -194,10 +206,10 @@ pub enum AttesterSlashingInvalid {
AttestationDataIdentical,
/// The attestations were not in conflict.
NotSlashable,
/// The first `SlashableAttestation` was invalid.
SlashableAttestation1Invalid(SlashableAttestationInvalid),
/// The second `SlashableAttestation` was invalid.
SlashableAttestation2Invalid(SlashableAttestationInvalid),
/// The first `IndexedAttestation` was invalid.
IndexedAttestation1Invalid(IndexedAttestationInvalid),
/// The second `IndexedAttestation` was invalid.
IndexedAttestation2Invalid(IndexedAttestationInvalid),
/// The validator index is unknown. One cannot slash one who does not exist.
UnknownValidator(u64),
/// The specified validator has already been withdrawn.
@ -210,52 +222,50 @@ impl_from_beacon_state_error!(AttesterSlashingValidationError);
impl_into_with_index_with_beacon_error!(AttesterSlashingValidationError, AttesterSlashingInvalid);
/*
* `SlashableAttestation` Validation
* `IndexedAttestation` Validation
*/
/// The object is invalid or validation failed.
#[derive(Debug, PartialEq)]
pub enum SlashableAttestationValidationError {
pub enum IndexedAttestationValidationError {
/// Validation completed successfully and the object is invalid.
Invalid(SlashableAttestationInvalid),
Invalid(IndexedAttestationInvalid),
}
/// Describes why an object is invalid.
#[derive(Debug, PartialEq)]
pub enum SlashableAttestationInvalid {
pub enum IndexedAttestationInvalid {
/// The custody bit 0 validators intersect with the bit 1 validators.
CustodyBitValidatorsIntersect,
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
CustodyBitfieldHasSetBits,
/// No validator indices were specified.
NoValidatorIndices,
/// The number of indices exceeds the global maximum.
///
/// (max_indices, indices_given)
MaxIndicesExceed(u64, usize),
/// The validator indices were not in increasing order.
///
/// The error occured between the given `index` and `index + 1`
BadValidatorIndicesOrdering(usize),
/// The custody bitfield length is not the smallest possible size to represent the validators.
///
/// (validators_len, bitfield_len)
BadCustodyBitfieldLength(usize, usize),
/// The number of slashable indices exceed the global maximum.
///
/// (max_indices, indices_given)
MaxIndicesExceed(usize, usize),
/// The validator index is unknown. One cannot slash one who does not exist.
UnknownValidator(u64),
/// The slashable attestation aggregate signature was not valid.
/// The indexed attestation aggregate signature was not valid.
BadSignature,
}
impl Into<SlashableAttestationInvalid> for SlashableAttestationValidationError {
fn into(self) -> SlashableAttestationInvalid {
impl Into<IndexedAttestationInvalid> for IndexedAttestationValidationError {
fn into(self) -> IndexedAttestationInvalid {
match self {
SlashableAttestationValidationError::Invalid(e) => e,
IndexedAttestationValidationError::Invalid(e) => e,
}
}
}
impl_into_with_index_without_beacon_error!(
SlashableAttestationValidationError,
SlashableAttestationInvalid
IndexedAttestationValidationError,
IndexedAttestationInvalid
);
/*
@ -280,10 +290,8 @@ pub enum ProposerSlashingInvalid {
ProposalEpochMismatch(Slot, Slot),
/// 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 specified proposer cannot be slashed because they are already slashed, or not active.
ProposerNotSlashable(u64),
/// The first proposal signature was invalid.
BadProposal1Signature,
/// The second proposal signature was invalid.
@ -313,11 +321,8 @@ pub enum DepositValidationError {
pub enum DepositInvalid {
/// The deposit index does not match the state index.
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
/// credentials of an existing validator with the same public key.
BadWithdrawalCredentials,
/// The signature (proof-of-possession) does not match the given pubkey.
BadSignature,
/// The specified `branch` and `index` did not form a valid proof that the deposit is included
/// in the eth1 deposit root.
BadMerkleProof,
@ -340,6 +345,8 @@ pub enum ExitValidationError {
/// Describes why an object is invalid.
#[derive(Debug, PartialEq)]
pub enum ExitInvalid {
/// The specified validator is not active.
NotActive(u64),
/// The specified validator is not in the state's validator registry.
ValidatorUnknown(u64),
/// The specified validator has a non-maximum exit epoch.
@ -388,7 +395,12 @@ pub enum TransferInvalid {
/// min_deposit_amount`
///
/// (resulting_amount, min_deposit_amount)
InvalidResultingFromBalance(u64, u64),
SenderDust(u64, u64),
/// This transfer would result in the `transfer.to` account to have `0 < balance <
/// min_deposit_amount`
///
/// (resulting_amount, min_deposit_amount)
RecipientDust(u64, u64),
/// The state slot does not match `transfer.slot`.
///
/// (state_slot, transfer_slot)

View File

@ -67,7 +67,7 @@ fn invalid_block_signature() {
let keypair = Keypair::random();
let message = block.signed_root();
let epoch = block.slot.epoch(spec.slots_per_epoch);
let domain = spec.get_domain(epoch, Domain::BeaconBlock, &state.fork);
let domain = spec.get_domain(epoch, Domain::BeaconProposer, &state.fork);
block.signature = Signature::new(&message, domain, &keypair.sk);
// process block with invalid block signature

View File

@ -1,5 +1,8 @@
use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
use crate::common::verify_bitfield_length;
use crate::common::convert_to_indexed;
use crate::per_block_processing::{
verify_indexed_attestation, verify_indexed_attestation_without_signature,
};
use tree_hash::TreeHash;
use types::*;
@ -8,7 +11,7 @@ use types::*;
///
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn validate_attestation<T: EthSpec>(
state: &BeaconState<T>,
attestation: &Attestation,
@ -31,7 +34,7 @@ pub fn validate_attestation_time_independent_only<T: EthSpec>(
///
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn validate_attestation_without_signature<T: EthSpec>(
state: &BeaconState<T>,
attestation: &Attestation,
@ -44,7 +47,7 @@ pub fn validate_attestation_without_signature<T: EthSpec>(
/// given state, optionally validating the aggregate signature.
///
///
/// Spec v0.5.1
/// Spec v0.6.1
fn validate_attestation_parametric<T: EthSpec>(
state: &BeaconState<T>,
attestation: &Attestation,
@ -52,107 +55,29 @@ fn validate_attestation_parametric<T: EthSpec>(
verify_signature: bool,
time_independent_only: bool,
) -> Result<(), Error> {
// Can't submit pre-historic attestations.
verify!(
attestation.data.slot >= spec.genesis_slot,
Invalid::PreGenesis {
genesis: spec.genesis_slot,
attestation: attestation.data.slot
}
);
let attestation_slot = state.get_attestation_slot(&attestation.data)?;
// 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.
// Check attestation slot.
verify!(
time_independent_only
|| attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|| attestation_slot + spec.min_attestation_inclusion_delay <= state.slot,
Invalid::IncludedTooEarly {
state: state.slot,
delay: spec.min_attestation_inclusion_delay,
attestation: attestation.data.slot
attestation: attestation_slot
}
);
verify!(
state.slot <= attestation_slot + spec.slots_per_epoch,
Invalid::IncludedTooLate {
state: state.slot,
attestation: attestation_slot
}
);
// Verify the justified epoch and root is correct.
// Verify the Casper FFG vote.
if !time_independent_only {
verify_justified_epoch_and_root(attestation, state, spec)?;
}
// Check that the crosslink data is valid.
//
// Verify that either:
//
// (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`,
//
// (ii) `state.latest_crosslinks[attestation.data.shard] ==
// Crosslink(crosslink_data_root=attestation.data.crosslink_data_root,
// epoch=slot_to_epoch(attestation.data.slot))`.
let potential_crosslink = Crosslink {
crosslink_data_root: attestation.data.crosslink_data_root,
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
};
verify!(
(attestation.data.previous_crosslink
== state.latest_crosslinks[attestation.data.shard as usize])
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
Invalid::BadPreviousCrosslink
);
// 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: committee.len(),
bitfield_len: attestation.custody_bitfield.len()
}
);
if verify_signature {
verify_attestation_signature(state, committee, attestation, spec)?;
verify_casper_ffg_vote(attestation, state)?;
}
// Crosslink data root is zero (to be removed in phase 1).
@ -161,145 +86,71 @@ fn validate_attestation_parametric<T: EthSpec>(
Invalid::ShardBlockRootNotZero
);
// Check signature and bitfields
let indexed_attestation = convert_to_indexed(state, attestation)?;
if verify_signature {
verify_indexed_attestation(state, &indexed_attestation, spec)?;
} else {
verify_indexed_attestation_without_signature(state, &indexed_attestation, spec)?;
}
Ok(())
}
/// Verify that the `source_epoch` and `source_root` of an `Attestation` correctly
/// match the current (or previous) justified epoch and root from the state.
/// Check target epoch, source epoch, source root, and source crosslink.
///
/// Spec v0.5.1
fn verify_justified_epoch_and_root<T: EthSpec>(
/// Spec v0.6.1
fn verify_casper_ffg_vote<T: EthSpec>(
attestation: &Attestation,
state: &BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
let state_epoch = state.slot.epoch(spec.slots_per_epoch);
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
if attestation_epoch >= state_epoch {
let data = &attestation.data;
if data.target_epoch == state.current_epoch() {
verify!(
attestation.data.source_epoch == state.current_justified_epoch,
data.source_epoch == state.current_justified_epoch,
Invalid::WrongJustifiedEpoch {
state: state.current_justified_epoch,
attestation: attestation.data.source_epoch,
attestation: data.source_epoch,
is_current: true,
}
);
verify!(
attestation.data.source_root == state.current_justified_root,
data.source_root == state.current_justified_root,
Invalid::WrongJustifiedRoot {
state: state.current_justified_root,
attestation: attestation.data.source_root,
attestation: data.source_root,
is_current: true,
}
);
} else {
verify!(
attestation.data.source_epoch == state.previous_justified_epoch,
data.previous_crosslink_root
== Hash256::from_slice(&state.get_current_crosslink(data.shard)?.tree_hash_root()),
Invalid::BadPreviousCrosslink
);
} else if data.target_epoch == state.previous_epoch() {
verify!(
data.source_epoch == state.previous_justified_epoch,
Invalid::WrongJustifiedEpoch {
state: state.previous_justified_epoch,
attestation: attestation.data.source_epoch,
attestation: data.source_epoch,
is_current: false,
}
);
verify!(
attestation.data.source_root == state.previous_justified_root,
data.source_root == state.previous_justified_root,
Invalid::WrongJustifiedRoot {
state: state.previous_justified_root,
attestation: attestation.data.source_root,
is_current: true,
attestation: data.source_root,
is_current: false,
}
);
verify!(
data.previous_crosslink_root
== Hash256::from_slice(&state.get_previous_crosslink(data.shard)?.tree_hash_root()),
Invalid::BadPreviousCrosslink
);
} else {
invalid!(Invalid::BadTargetEpoch)
}
Ok(())
}
/// Verifies an aggregate signature for some given `AttestationData`, returning `true` if the
/// `aggregate_signature` is valid.
///
/// Returns `false` if:
/// - `aggregate_signature` was not signed correctly.
/// - `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.5.1
fn verify_attestation_signature<T: EthSpec>(
state: &BeaconState<T>,
committee: &[usize],
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 = 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 a.custody_bitfield.get(i) {
Ok(bit) => bit,
// Invalidate signature if custody_bitfield.len() < committee
Err(_) => {
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength {
committee_len: committee.len(),
bitfield_len: a.aggregation_bitfield.len(),
}));
}
};
message_exists[custody_bit as usize] = true;
match state.validator_registry.get(*v as usize) {
Some(validator) => {
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
}
// Return error if validator index is unknown.
None => return Err(Error::BeaconStateError(BeaconStateError::UnknownValidator)),
};
}
}
// Message when custody bitfield is `false`
let message_0 = AttestationDataAndCustodyBit {
data: a.data.clone(),
custody_bit: false,
}
.tree_hash_root();
// Message when custody bitfield is `true`
let message_1 = AttestationDataAndCustodyBit {
data: a.data.clone(),
custody_bit: true,
}
.tree_hash_root();
let mut messages = vec![];
let mut keys = vec![];
// If any validator signed a message with a `false` custody bit.
if message_exists[0] {
messages.push(&message_0[..]);
keys.push(&aggregate_pubs[0]);
}
// If any validator signed a message with a `true` custody bit.
if message_exists[1] {
messages.push(&message_1[..]);
keys.push(&aggregate_pubs[1]);
}
let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork);
verify!(
a.aggregate_signature
.verify_multiple(&messages[..], domain, &keys[..]),
Invalid::BadSignature
);
Ok(())
}

View File

@ -1,5 +1,6 @@
use super::errors::{AttesterSlashingInvalid as Invalid, AttesterSlashingValidationError as Error};
use super::verify_slashable_attestation::verify_slashable_attestation;
use super::verify_indexed_attestation::verify_indexed_attestation;
use std::collections::BTreeSet;
use types::*;
/// Indicates if an `AttesterSlashing` is valid to be included in a block in the current epoch of the given
@ -7,90 +8,87 @@ use types::*;
///
/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn verify_attester_slashing<T: EthSpec>(
state: &BeaconState<T>,
attester_slashing: &AttesterSlashing,
should_verify_slashable_attestations: bool,
should_verify_indexed_attestations: bool,
spec: &ChainSpec,
) -> Result<(), Error> {
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
let attestation_1 = &attester_slashing.attestation_1;
let attestation_2 = &attester_slashing.attestation_2;
// Spec: is_slashable_attestation_data
verify!(
slashable_attestation_1.data != slashable_attestation_2.data,
Invalid::AttestationDataIdentical
);
verify!(
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
attestation_1.is_double_vote(attestation_2)
|| attestation_1.is_surround_vote(attestation_2),
Invalid::NotSlashable
);
if should_verify_slashable_attestations {
verify_slashable_attestation(state, &slashable_attestation_1, spec)
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?;
verify_slashable_attestation(state, &slashable_attestation_2, spec)
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?;
if should_verify_indexed_attestations {
verify_indexed_attestation(state, &attestation_1, spec)
.map_err(|e| Error::Invalid(Invalid::IndexedAttestation1Invalid(e.into())))?;
verify_indexed_attestation(state, &attestation_2, spec)
.map_err(|e| Error::Invalid(Invalid::IndexedAttestation2Invalid(e.into())))?;
}
Ok(())
}
/// For a given attester slashing, return the indices able to be slashed.
/// For a given attester slashing, return the indices able to be slashed in ascending order.
///
/// Returns Ok(indices) if `indices.len() > 0`.
///
/// Spec v0.5.1
pub fn gather_attester_slashing_indices<T: EthSpec>(
/// Spec v0.6.1
pub fn get_slashable_indices<T: EthSpec>(
state: &BeaconState<T>,
attester_slashing: &AttesterSlashing,
spec: &ChainSpec,
) -> Result<Vec<u64>, Error> {
gather_attester_slashing_indices_modular(
state,
attester_slashing,
|_, validator| validator.slashed,
spec,
)
get_slashable_indices_modular(state, attester_slashing, |_, validator| {
validator.is_slashable_at(state.current_epoch())
})
}
/// Same as `gather_attester_slashing_indices` but allows the caller to specify the criteria
/// for determining whether a given validator should be considered slashed.
pub fn gather_attester_slashing_indices_modular<F, T: EthSpec>(
/// for determining whether a given validator should be considered slashable.
pub fn get_slashable_indices_modular<F, T: EthSpec>(
state: &BeaconState<T>,
attester_slashing: &AttesterSlashing,
is_slashed: F,
spec: &ChainSpec,
is_slashable: F,
) -> Result<Vec<u64>, Error>
where
F: Fn(u64, &Validator) -> bool,
{
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
let attestation_1 = &attester_slashing.attestation_1;
let attestation_2 = &attester_slashing.attestation_2;
let mut slashable_indices = Vec::with_capacity(spec.max_indices_per_slashable_vote);
for i in &slashable_attestation_1.validator_indices {
let attesting_indices_1 = attestation_1
.custody_bit_0_indices
.iter()
.chain(&attestation_1.custody_bit_1_indices)
.cloned()
.collect::<BTreeSet<_>>();
let attesting_indices_2 = attestation_2
.custody_bit_0_indices
.iter()
.chain(&attestation_2.custody_bit_1_indices)
.cloned()
.collect::<BTreeSet<_>>();
let mut slashable_indices = vec![];
for index in &attesting_indices_1 & &attesting_indices_2 {
let validator = state
.validator_registry
.get(*i as usize)
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?;
.get(index as usize)
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(index)))?;
if slashable_attestation_2.validator_indices.contains(&i) & !is_slashed(*i, validator) {
// 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);
if is_slashable(index, validator) {
slashable_indices.push(index);
}
}
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
slashable_indices.shrink_to_fit();
Ok(slashable_indices)
}

View File

@ -1,52 +1,31 @@
use super::errors::{DepositInvalid as Invalid, DepositValidationError as Error};
use hashing::hash;
use merkle_proof::verify_merkle_proof;
use ssz::ssz_encode;
use ssz_derive::Encode;
use tree_hash::{SignedRoot, TreeHash};
use types::*;
/// Indicates if a `Deposit` is valid to be included in a block in the current epoch of the given
/// state.
/// Verify `Deposit.pubkey` signed `Deposit.signature`.
///
/// Returns `Ok(())` if the `Deposit` is valid, otherwise indicates the reason for invalidity.
///
/// This function _does not_ check `state.deposit_index` so this function may be run in parallel.
/// See the `verify_deposit_index` function for this.
///
/// Note: this function is incomplete.
///
/// Spec v0.5.1
pub fn verify_deposit<T: EthSpec>(
/// Spec v0.6.1
pub fn verify_deposit_signature<T: EthSpec>(
state: &BeaconState<T>,
deposit: &Deposit,
verify_merkle_branch: bool,
spec: &ChainSpec,
) -> Result<(), Error> {
verify!(
deposit
.deposit_data
.deposit_input
.validate_proof_of_possession(
state.slot.epoch(spec.slots_per_epoch),
&state.fork,
spec
),
Invalid::BadProofOfPossession
deposit.data.signature.verify(
&deposit.data.signed_root(),
spec.get_domain(state.current_epoch(), Domain::Deposit, &state.fork),
&deposit.data.pubkey,
),
Invalid::BadSignature
);
if verify_merkle_branch {
verify!(
verify_deposit_merkle_proof(state, deposit, spec),
Invalid::BadMerkleProof
);
}
Ok(())
}
/// Verify that the `Deposit` index is correct.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn verify_deposit_index<T: EthSpec>(
state: &BeaconState<T>,
deposit: &Deposit,
@ -72,61 +51,30 @@ pub fn get_existing_validator_index<T: EthSpec>(
state: &BeaconState<T>,
deposit: &Deposit,
) -> Result<Option<u64>, Error> {
let deposit_input = &deposit.deposit_data.deposit_input;
let validator_index = state.get_validator_index(&deposit_input.pubkey)?;
match validator_index {
None => Ok(None),
Some(index) => {
verify!(
deposit_input.withdrawal_credentials
== state.validator_registry[index as usize].withdrawal_credentials,
Invalid::BadWithdrawalCredentials
);
Ok(Some(index as u64))
}
}
let validator_index = state.get_validator_index(&deposit.data.pubkey)?;
Ok(validator_index.map(|idx| idx as u64))
}
/// Verify that a deposit is included in the state's eth1 deposit root.
///
/// Spec v0.5.1
fn verify_deposit_merkle_proof<T: EthSpec>(
/// Spec v0.6.1
pub fn verify_deposit_merkle_proof<T: EthSpec>(
state: &BeaconState<T>,
deposit: &Deposit,
spec: &ChainSpec,
) -> bool {
let leaf = hash(&get_serialized_deposit_data(deposit));
verify_merkle_proof(
Hash256::from_slice(&leaf),
&deposit.proof[..],
spec.deposit_contract_tree_depth as usize,
deposit.index as usize,
state.latest_eth1_data.deposit_root,
)
}
) -> Result<(), Error> {
let leaf = deposit.data.tree_hash_root();
/// Helper struct for easily getting the serialized data generated by the deposit contract.
///
/// Spec v0.5.1
#[derive(Encode)]
struct SerializedDepositData {
amount: u64,
timestamp: u64,
input: DepositInput,
}
verify!(
verify_merkle_proof(
Hash256::from_slice(&leaf),
&deposit.proof[..],
spec.deposit_contract_tree_depth as usize,
deposit.index as usize,
state.latest_eth1_data.deposit_root,
),
Invalid::BadMerkleProof
);
/// Return the serialized data generated by the deposit contract that is used to generate the
/// merkle proof.
///
/// Spec v0.5.1
fn get_serialized_deposit_data(deposit: &Deposit) -> Vec<u8> {
let serialized_deposit_data = SerializedDepositData {
amount: deposit.deposit_data.amount,
timestamp: deposit.deposit_data.timestamp,
input: deposit.deposit_data.deposit_input.clone(),
};
ssz_encode(&serialized_deposit_data)
Ok(())
}

View File

@ -7,7 +7,7 @@ use types::*;
///
/// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn verify_exit<T: EthSpec>(
state: &BeaconState<T>,
exit: &VoluntaryExit,
@ -17,6 +17,8 @@ pub fn verify_exit<T: EthSpec>(
}
/// Like `verify_exit` but doesn't run checks which may become true in future states.
///
/// Spec v0.6.1
pub fn verify_exit_time_independent_only<T: EthSpec>(
state: &BeaconState<T>,
exit: &VoluntaryExit,
@ -26,6 +28,8 @@ pub fn verify_exit_time_independent_only<T: EthSpec>(
}
/// Parametric version of `verify_exit` that skips some checks if `time_independent_only` is true.
///
/// Spec v0.6.1
fn verify_exit_parametric<T: EthSpec>(
state: &BeaconState<T>,
exit: &VoluntaryExit,
@ -37,29 +41,29 @@ fn verify_exit_parametric<T: EthSpec>(
.get(exit.validator_index as usize)
.ok_or_else(|| Error::Invalid(Invalid::ValidatorUnknown(exit.validator_index)))?;
// Verify the validator is active.
verify!(
validator.is_active_at(state.current_epoch()),
Invalid::NotActive(exit.validator_index)
);
// Verify that the validator has not yet exited.
verify!(
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!(
time_independent_only || state.current_epoch(spec) >= exit.epoch,
time_independent_only || state.current_epoch() >= exit.epoch,
Invalid::FutureEpoch {
state: state.current_epoch(spec),
state: state.current_epoch(),
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 the validator has been active long enough.
let lifespan = state.current_epoch() - validator.activation_epoch;
verify!(
lifespan >= spec.persistent_committee_period,
Invalid::TooYoungToLeave {
@ -68,9 +72,9 @@ fn verify_exit_parametric<T: EthSpec>(
}
);
// Verify signature.
let message = exit.signed_root();
let domain = spec.get_domain(exit.epoch, Domain::Exit, &state.fork);
let domain = spec.get_domain(exit.epoch, Domain::VoluntaryExit, &state.fork);
verify!(
exit.signature
.verify(&message[..], domain, &validator.pubkey),

View File

@ -0,0 +1,156 @@
use super::errors::{
IndexedAttestationInvalid as Invalid, IndexedAttestationValidationError as Error,
};
use std::collections::HashSet;
use std::iter::FromIterator;
use tree_hash::TreeHash;
use types::*;
/// Verify an `IndexedAttestation`.
///
/// Spec v0.6.1
pub fn verify_indexed_attestation<T: EthSpec>(
state: &BeaconState<T>,
indexed_attestation: &IndexedAttestation,
spec: &ChainSpec,
) -> Result<(), Error> {
verify_indexed_attestation_parametric(state, indexed_attestation, spec, true)
}
/// Verify but don't check the signature.
///
/// Spec v0.6.1
pub fn verify_indexed_attestation_without_signature<T: EthSpec>(
state: &BeaconState<T>,
indexed_attestation: &IndexedAttestation,
spec: &ChainSpec,
) -> Result<(), Error> {
verify_indexed_attestation_parametric(state, indexed_attestation, spec, false)
}
/// Optionally check the signature.
///
/// Spec v0.6.1
fn verify_indexed_attestation_parametric<T: EthSpec>(
state: &BeaconState<T>,
indexed_attestation: &IndexedAttestation,
spec: &ChainSpec,
verify_signature: bool,
) -> Result<(), Error> {
let custody_bit_0_indices = &indexed_attestation.custody_bit_0_indices;
let custody_bit_1_indices = &indexed_attestation.custody_bit_1_indices;
// Ensure no duplicate indices across custody bits
let custody_bit_intersection: HashSet<&u64> =
&HashSet::from_iter(custody_bit_0_indices) & &HashSet::from_iter(custody_bit_1_indices);
verify!(
custody_bit_intersection.is_empty(),
Invalid::CustodyBitValidatorsIntersect
);
// Check that nobody signed with custody bit 1 (to be removed in phase 1)
if custody_bit_1_indices.len() > 0 {
invalid!(Invalid::CustodyBitfieldHasSetBits);
}
let total_indices = custody_bit_0_indices.len() + custody_bit_1_indices.len();
verify!(1 <= total_indices, Invalid::NoValidatorIndices);
verify!(
total_indices as u64 <= spec.max_indices_per_attestation,
Invalid::MaxIndicesExceed(spec.max_indices_per_attestation, total_indices)
);
// Check that both vectors of indices are sorted
let check_sorted = |list: &Vec<u64>| {
list.windows(2).enumerate().try_for_each(|(i, pair)| {
if pair[0] >= pair[1] {
invalid!(Invalid::BadValidatorIndicesOrdering(i));
} else {
Ok(())
}
})?;
Ok(())
};
check_sorted(custody_bit_0_indices)?;
check_sorted(custody_bit_1_indices)?;
if verify_signature {
verify_indexed_attestation_signature(state, indexed_attestation, spec)?;
}
Ok(())
}
/// Create an aggregate public key for a list of validators, failing if any key can't be found.
fn create_aggregate_pubkey<'a, T, I>(
state: &BeaconState<T>,
validator_indices: I,
) -> Result<AggregatePublicKey, Error>
where
I: IntoIterator<Item = &'a u64>,
T: EthSpec,
{
validator_indices.into_iter().try_fold(
AggregatePublicKey::new(),
|mut aggregate_pubkey, &validator_idx| {
state
.validator_registry
.get(validator_idx as usize)
.ok_or(Error::Invalid(Invalid::UnknownValidator(validator_idx)))
.map(|validator| {
aggregate_pubkey.add(&validator.pubkey);
aggregate_pubkey
})
},
)
}
/// Verify the signature of an IndexedAttestation.
///
/// Spec v0.6.1
fn verify_indexed_attestation_signature<T: EthSpec>(
state: &BeaconState<T>,
indexed_attestation: &IndexedAttestation,
spec: &ChainSpec,
) -> Result<(), Error> {
let bit_0_pubkey = create_aggregate_pubkey(state, &indexed_attestation.custody_bit_0_indices)?;
let bit_1_pubkey = create_aggregate_pubkey(state, &indexed_attestation.custody_bit_1_indices)?;
let message_0 = AttestationDataAndCustodyBit {
data: indexed_attestation.data.clone(),
custody_bit: false,
}
.tree_hash_root();
let message_1 = AttestationDataAndCustodyBit {
data: indexed_attestation.data.clone(),
custody_bit: true,
}
.tree_hash_root();
let mut messages = vec![];
let mut keys = vec![];
if !indexed_attestation.custody_bit_0_indices.is_empty() {
messages.push(&message_0[..]);
keys.push(&bit_0_pubkey);
}
if !indexed_attestation.custody_bit_1_indices.is_empty() {
messages.push(&message_1[..]);
keys.push(&bit_1_pubkey);
}
let domain = spec.get_domain(
indexed_attestation.data.target_epoch,
Domain::Attestation,
&state.fork,
);
verify!(
indexed_attestation
.signature
.verify_multiple(&messages[..], domain, &keys[..]),
Invalid::BadSignature
);
Ok(())
}

View File

@ -7,7 +7,7 @@ use types::*;
///
/// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn verify_proposer_slashing<T: EthSpec>(
proposer_slashing: &ProposerSlashing,
state: &BeaconState<T>,
@ -34,11 +34,9 @@ pub fn verify_proposer_slashing<T: EthSpec>(
Invalid::ProposalsIdentical
);
verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed);
verify!(
proposer.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
Invalid::ProposerAlreadyWithdrawn(proposer_slashing.proposer_index)
proposer.is_slashable_at(state.current_epoch()),
Invalid::ProposerNotSlashable(proposer_slashing.proposer_index)
);
verify!(
@ -67,7 +65,7 @@ pub fn verify_proposer_slashing<T: EthSpec>(
///
/// Returns `true` if the signature is valid.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn verify_header_signature(
header: &BeaconBlockHeader,
pubkey: &PublicKey,
@ -77,7 +75,7 @@ fn verify_header_signature(
let message = header.signed_root();
let domain = spec.get_domain(
header.slot.epoch(spec.slots_per_epoch),
Domain::BeaconBlock,
Domain::BeaconProposer,
fork,
);
header.signature.verify(&message[..], domain, pubkey)

View File

@ -1,112 +0,0 @@
use super::errors::{
SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error,
};
use crate::common::verify_bitfield_length;
use tree_hash::TreeHash;
use types::*;
/// Indicates if a `SlashableAttestation` is valid to be included in a block in the current epoch of the given
/// state.
///
/// Returns `Ok(())` if the `SlashableAttestation` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.5.1
pub fn verify_slashable_attestation<T: EthSpec>(
state: &BeaconState<T>,
slashable_attestation: &SlashableAttestation,
spec: &ChainSpec,
) -> Result<(), Error> {
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
invalid!(Invalid::CustodyBitfieldHasSetBits);
}
if slashable_attestation.validator_indices.is_empty() {
invalid!(Invalid::NoValidatorIndices);
}
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
if slashable_attestation.validator_indices[i]
>= slashable_attestation.validator_indices[i + 1]
{
invalid!(Invalid::BadValidatorIndicesOrdering(i));
}
}
if !verify_bitfield_length(
&slashable_attestation.custody_bitfield,
slashable_attestation.validator_indices.len(),
) {
invalid!(Invalid::BadCustodyBitfieldLength(
slashable_attestation.validator_indices.len(),
slashable_attestation.custody_bitfield.len()
));
}
if slashable_attestation.validator_indices.len() > spec.max_indices_per_slashable_vote as usize
{
invalid!(Invalid::MaxIndicesExceed(
spec.max_indices_per_slashable_vote as usize,
slashable_attestation.validator_indices.len()
));
}
// TODO: this signature verification could likely be replaced with:
//
// super::validate_attestation::validate_attestation_signature(..)
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
let mut message_exists = vec![false; 2];
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
Ok(bit) => bit,
Err(_) => unreachable!(),
};
message_exists[custody_bit as usize] = true;
match state.validator_registry.get(*v as usize) {
Some(validator) => {
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
}
None => invalid!(Invalid::UnknownValidator(*v)),
};
}
let message_0 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: false,
}
.tree_hash_root();
let message_1 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: true,
}
.tree_hash_root();
let mut messages = vec![];
let mut keys = vec![];
if message_exists[0] {
messages.push(&message_0[..]);
keys.push(&aggregate_pubs[0]);
}
if message_exists[1] {
messages.push(&message_1[..]);
keys.push(&aggregate_pubs[1]);
}
let domain = {
let epoch = slashable_attestation.data.slot.epoch(spec.slots_per_epoch);
spec.get_domain(epoch, Domain::Attestation, &state.fork)
};
verify!(
slashable_attestation
.aggregate_signature
.verify_multiple(&messages[..], domain, &keys[..]),
Invalid::BadSignature
);
Ok(())
}

View File

@ -8,9 +8,7 @@ use types::*;
///
/// Returns `Ok(())` if the `Transfer` is valid, otherwise indicates the reason for invalidity.
///
/// Note: this function is incomplete.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn verify_transfer<T: EthSpec>(
state: &BeaconState<T>,
transfer: &Transfer,
@ -20,6 +18,8 @@ pub fn verify_transfer<T: EthSpec>(
}
/// Like `verify_transfer` but doesn't run checks which may become true in future states.
///
/// Spec v0.6.1
pub fn verify_transfer_time_independent_only<T: EthSpec>(
state: &BeaconState<T>,
transfer: &Transfer,
@ -29,6 +29,15 @@ pub fn verify_transfer_time_independent_only<T: EthSpec>(
}
/// Parametric version of `verify_transfer` that allows some checks to be skipped.
///
/// When `time_independent_only == true`, time-specific parameters are ignored, including:
///
/// - Balance considerations (e.g., adequate balance, not dust, etc).
/// - `transfer.slot` does not have to exactly match `state.slot`, it just needs to be in the
/// present or future.
/// - Validator transfer eligibility (e.g., is withdrawable)
///
/// Spec v0.6.1
fn verify_transfer_parametric<T: EthSpec>(
state: &BeaconState<T>,
transfer: &Transfer,
@ -36,35 +45,44 @@ fn verify_transfer_parametric<T: EthSpec>(
time_independent_only: bool,
) -> Result<(), Error> {
let sender_balance = *state
.validator_balances
.balances
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let recipient_balance = *state
.balances
.get(transfer.recipient as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.recipient)))?;
// Safely determine `amount + fee`.
let total_amount = transfer
.amount
.checked_add(transfer.fee)
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
// Verify the sender has adequate balance.
verify!(
time_independent_only || sender_balance >= transfer.amount,
Invalid::FromBalanceInsufficient(transfer.amount, sender_balance)
);
verify!(
time_independent_only || sender_balance >= transfer.fee,
Invalid::FromBalanceInsufficient(transfer.fee, sender_balance)
);
// Verify sender balance will not be "dust" (i.e., greater than zero but less than the minimum deposit
// amount).
verify!(
time_independent_only
|| (sender_balance == total_amount)
|| (sender_balance >= (total_amount + spec.min_deposit_amount)),
Invalid::InvalidResultingFromBalance(
sender_balance - total_amount,
spec.min_deposit_amount
)
Invalid::SenderDust(sender_balance - total_amount, spec.min_deposit_amount)
);
// Verify the recipient balance will not be dust.
verify!(
time_independent_only || ((recipient_balance + transfer.amount) >= spec.min_deposit_amount),
Invalid::RecipientDust(sender_balance - total_amount, spec.min_deposit_amount)
);
// If loosely enforcing `transfer.slot`, ensure the slot is not in the past. Otherwise, ensure
// the transfer slot equals the state slot.
if time_independent_only {
verify!(
state.slot <= transfer.slot,
@ -77,19 +95,33 @@ fn verify_transfer_parametric<T: EthSpec>(
);
}
// Load the sender `Validator` record from the state.
let sender_validator = state
.validator_registry
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let epoch = state.slot.epoch(spec.slots_per_epoch);
// Ensure one of the following is met:
//
// - Time independent checks are being ignored.
// - The sender has not been activated.
// - The sender is withdrawable at the state's epoch.
// - The transfer will not reduce the sender below the max effective balance.
verify!(
time_independent_only
|| sender_validator.activation_eligibility_epoch == spec.far_future_epoch
|| sender_validator.is_withdrawable_at(epoch)
|| sender_validator.activation_epoch == spec.far_future_epoch,
|| total_amount + spec.max_effective_balance <= sender_balance,
Invalid::FromValidatorIneligableForTransfer(transfer.sender)
);
// Ensure the withdrawal credentials generated from the sender's pubkey match those stored in
// the validator registry.
//
// This ensures the validator can only perform a transfer when they are in control of the
// withdrawal address.
let transfer_withdrawal_credentials = Hash256::from_slice(
&get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..],
);
@ -101,13 +133,13 @@ fn verify_transfer_parametric<T: EthSpec>(
)
);
// Verify the transfer signature.
let message = transfer.signed_root();
let domain = spec.get_domain(
transfer.slot.epoch(spec.slots_per_epoch),
Domain::Transfer,
&state.fork,
);
verify!(
transfer
.signature
@ -122,31 +154,31 @@ fn verify_transfer_parametric<T: EthSpec>(
///
/// Does not check that the transfer is valid, however checks for overflow in all actions.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn execute_transfer<T: EthSpec>(
state: &mut BeaconState<T>,
transfer: &Transfer,
spec: &ChainSpec,
) -> Result<(), Error> {
let sender_balance = *state
.validator_balances
.balances
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let recipient_balance = *state
.validator_balances
.balances
.get(transfer.recipient as usize)
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.recipient)))?;
let proposer_index =
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
let proposer_balance = state.validator_balances[proposer_index];
let proposer_balance = state.balances[proposer_index];
let total_amount = transfer
.amount
.checked_add(transfer.fee)
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
state.validator_balances[transfer.sender as usize] =
state.balances[transfer.sender as usize] =
sender_balance.checked_sub(total_amount).ok_or_else(|| {
Error::Invalid(Invalid::FromBalanceInsufficient(
total_amount,
@ -154,7 +186,7 @@ pub fn execute_transfer<T: EthSpec>(
))
})?;
state.validator_balances[transfer.recipient as usize] = recipient_balance
state.balances[transfer.recipient as usize] = recipient_balance
.checked_add(transfer.amount)
.ok_or_else(|| {
Error::Invalid(Invalid::ToBalanceOverflow(
@ -163,7 +195,7 @@ pub fn execute_transfer<T: EthSpec>(
))
})?;
state.validator_balances[proposer_index] =
state.balances[proposer_index] =
proposer_balance.checked_add(transfer.fee).ok_or_else(|| {
Error::Invalid(Invalid::ProposerBalanceOverflow(
proposer_balance,

View File

@ -1,24 +1,18 @@
use apply_rewards::apply_rewards;
use apply_rewards::process_rewards_and_penalties;
use errors::EpochProcessingError as Error;
use process_ejections::process_ejections;
use process_exit_queue::process_exit_queue;
use process_slashings::process_slashings;
use registry_updates::process_registry_updates;
use std::collections::HashMap;
use tree_hash::TreeHash;
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 registry_updates;
pub mod tests;
pub mod update_registry_and_shuffling_data;
pub mod validator_statuses;
pub mod winning_root;
@ -32,14 +26,14 @@ 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.5.1
/// Spec v0.6.1
pub fn per_epoch_processing<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
// 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_committee_cache(RelativeEpoch::Previous, spec)?;
state.build_committee_cache(RelativeEpoch::Current, spec)?;
// Load the struct we use to assign validators into sets based on their participation.
//
@ -47,39 +41,28 @@ pub fn per_epoch_processing<T: EthSpec>(
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
validator_statuses.process_attestations(&state, spec)?;
// Justification.
update_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
// Justification and finalization.
process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
// Crosslinks.
let winning_root_for_shards = process_crosslinks(state, spec)?;
// Eth1 data.
maybe_reset_eth1_period(state, spec);
// Rewards and Penalities.
apply_rewards(
process_rewards_and_penalties(
state,
&mut validator_statuses,
&winning_root_for_shards,
spec,
)?;
// Ejections.
process_ejections(state, spec)?;
// Registry Updates.
process_registry_updates(state, spec)?;
// Validator Registry.
update_registry_and_shuffling_data(
state,
validator_statuses.total_balances.current_epoch,
spec,
)?;
// Slashings and exit queue.
// Slashings.
process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;
process_exit_queue(state, spec);
// Final updates.
finish_epoch_update(state, spec)?;
process_final_updates(state, spec)?;
// Rotate the epoch caches to suit the epoch transition.
state.advance_caches();
@ -87,89 +70,72 @@ pub fn per_epoch_processing<T: EthSpec>(
Ok(())
}
/// Maybe resets the eth1 period.
///
/// Spec v0.5.1
pub fn maybe_reset_eth1_period<T: EthSpec>(state: &mut BeaconState<T>, 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 * spec.slots_per_epoch {
state.latest_eth1_data = eth1_data_vote.eth1_data.clone();
}
}
state.eth1_data_votes = vec![];
}
}
/// Update the following fields on the `BeaconState`:
///
/// - `justification_bitfield`.
/// - `finalized_epoch`
/// - `justified_epoch`
/// - `previous_justified_epoch`
/// - `previous_justified_root`
/// - `current_justified_epoch`
/// - `current_justified_root`
/// - `finalized_epoch`
/// - `finalized_root`
///
/// Spec v0.5.1
pub fn update_justification_and_finalization<T: EthSpec>(
/// Spec v0.6.1
pub fn process_justification_and_finalization<T: EthSpec>(
state: &mut BeaconState<T>,
total_balances: &TotalBalances,
spec: &ChainSpec,
) -> Result<(), Error> {
let previous_epoch = state.previous_epoch(spec);
let current_epoch = state.current_epoch(spec);
if state.current_epoch() == spec.genesis_epoch {
return Ok(());
}
let mut new_justified_epoch = state.current_justified_epoch;
let mut new_finalized_epoch = state.finalized_epoch;
let previous_epoch = state.previous_epoch();
let current_epoch = state.current_epoch();
// Rotate the justification bitfield up one epoch to make room for the current epoch.
let old_previous_justified_epoch = state.previous_justified_epoch;
let old_current_justified_epoch = state.current_justified_epoch;
// Process justifications
state.previous_justified_epoch = state.current_justified_epoch;
state.previous_justified_root = state.current_justified_root;
state.justification_bitfield <<= 1;
// If the previous epoch gets justified, full the second last bit.
if (total_balances.previous_epoch_boundary_attesters * 3) >= (total_balances.previous_epoch * 2)
{
new_justified_epoch = previous_epoch;
if total_balances.previous_epoch_target_attesters * 3 >= total_balances.previous_epoch * 2 {
state.current_justified_epoch = previous_epoch;
state.current_justified_root =
*state.get_block_root_at_epoch(state.current_justified_epoch, spec)?;
state.justification_bitfield |= 2;
}
// 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;
if total_balances.current_epoch_target_attesters * 3 >= total_balances.current_epoch * 2 {
state.current_justified_epoch = current_epoch;
state.current_justified_root =
*state.get_block_root_at_epoch(state.current_justified_epoch, spec)?;
state.justification_bitfield |= 1;
}
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 (bitfield >> 1) % 8 == 0b111 && old_previous_justified_epoch == current_epoch - 3 {
state.finalized_epoch = old_previous_justified_epoch;
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
}
// 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 (bitfield >> 1) % 4 == 0b11 && state.previous_justified_epoch == current_epoch - 2 {
state.finalized_epoch = old_previous_justified_epoch;
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
}
// 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 bitfield % 8 == 0b111 && state.current_justified_epoch == current_epoch - 2 {
state.finalized_epoch = old_current_justified_epoch;
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
}
// 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.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))?;
}
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))?;
if bitfield % 4 == 0b11 && state.current_justified_epoch == current_epoch - 1 {
state.finalized_epoch = old_current_justified_epoch;
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
}
Ok(())
@ -177,42 +143,36 @@ pub fn update_justification_and_finalization<T: EthSpec>(
/// Updates the following fields on the `BeaconState`:
///
/// - `latest_crosslinks`
/// - `previous_crosslinks`
/// - `current_crosslinks`
///
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_crosslinks<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<WinningRootHashSet, Error> {
let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();
let previous_and_current_epoch_slots: Vec<Slot> = state
.previous_epoch(spec)
.slot_iter(spec.slots_per_epoch)
.chain(state.current_epoch(spec).slot_iter(spec.slots_per_epoch))
.collect();
state.previous_crosslinks = state.current_crosslinks.clone();
for slot in previous_and_current_epoch_slots {
// Clone removes the borrow which becomes an issue when mutating `state.balances`.
let crosslink_committees_at_slot =
state.get_crosslink_committees_at_slot(slot, spec)?.clone();
for relative_epoch in vec![RelativeEpoch::Previous, RelativeEpoch::Current] {
let epoch = relative_epoch.into_epoch(state.current_epoch());
for offset in 0..state.get_epoch_committee_count(relative_epoch)? {
let shard =
(state.get_epoch_start_shard(relative_epoch)? + offset) % T::ShardCount::to_u64();
let crosslink_committee =
state.get_crosslink_committee_for_shard(shard, relative_epoch)?;
for c in crosslink_committees_at_slot {
let shard = c.shard as u64;
let winning_root = winning_root(state, shard, spec)?;
let winning_root = winning_root(state, shard, epoch, spec)?;
if let Some(winning_root) = winning_root {
let total_committee_balance = state.get_total_balance(&c.committee, spec)?;
let total_committee_balance =
state.get_total_balance(&crosslink_committee.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: slot.epoch(spec.slots_per_epoch),
crosslink_data_root: winning_root.crosslink_data_root,
}
if 3 * winning_root.total_attesting_balance >= 2 * total_committee_balance {
state.current_crosslinks[shard as usize] = winning_root.crosslink.clone();
}
winning_root_for_shards.insert(shard, winning_root);
}
@ -224,13 +184,35 @@ pub fn process_crosslinks<T: EthSpec>(
/// Finish up an epoch update.
///
/// Spec v0.5.1
pub fn finish_epoch_update<T: EthSpec>(
/// Spec v0.6.1
pub fn process_final_updates<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec);
let next_epoch = state.next_epoch(spec);
let current_epoch = state.current_epoch();
let next_epoch = state.next_epoch();
// Reset eth1 data votes.
if (state.slot + 1) % spec.slots_per_eth1_voting_period == 0 {
state.eth1_data_votes = vec![];
}
// Update effective balances with hysteresis (lag).
for (index, validator) in state.validator_registry.iter_mut().enumerate() {
let balance = state.balances[index];
let half_increment = spec.effective_balance_increment / 2;
if balance < validator.effective_balance
|| validator.effective_balance + 3 * half_increment < balance
{
validator.effective_balance = std::cmp::min(
balance - balance % spec.effective_balance_increment,
spec.max_effective_balance,
);
}
}
// Update start shard.
state.latest_start_shard = state.next_epoch_start_shard()?;
// This is a hack to allow us to update index roots and slashed balances for the next epoch.
//
@ -250,11 +232,7 @@ pub fn finish_epoch_update<T: EthSpec>(
state.set_slashed_balance(next_epoch, state.get_slashed_balance(current_epoch)?)?;
// Set randao mix
state.set_randao_mix(
next_epoch,
*state.get_randao_mix(current_epoch, spec)?,
spec,
)?;
state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;
state.slot -= 1;
}
@ -266,8 +244,9 @@ pub fn finish_epoch_update<T: EthSpec>(
.push(Hash256::from_slice(&historical_batch.tree_hash_root()[..]));
}
state.previous_epoch_attestations = state.current_epoch_attestations.clone();
state.current_epoch_attestations = vec![];
// Rotate current/previous epoch attestations
state.previous_epoch_attestations =
std::mem::replace(&mut state.current_epoch_attestations, vec![]);
Ok(())
}

View File

@ -32,57 +32,52 @@ impl std::ops::AddAssign for Delta {
/// Apply attester and proposer rewards.
///
/// Spec v0.5.1
pub fn apply_rewards<T: EthSpec>(
/// Spec v0.6.1
pub fn process_rewards_and_penalties<T: EthSpec>(
state: &mut BeaconState<T>,
validator_statuses: &mut ValidatorStatuses,
winning_root_for_shards: &WinningRootHashSet,
spec: &ChainSpec,
) -> Result<(), Error> {
if state.current_epoch() == spec.genesis_epoch {
return Ok(());
}
// 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() {
if validator_statuses.statuses.len() != state.balances.len()
|| validator_statuses.statuses.len() != state.validator_registry.len()
{
return Err(Error::ValidatorStatusesInconsistent);
}
let mut deltas = vec![Delta::default(); state.validator_balances.len()];
let mut deltas = vec![Delta::default(); state.balances.len()];
get_justification_and_finalization_deltas(&mut deltas, state, &validator_statuses, spec)?;
get_attestation_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,
)?;
}
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);
state.balances[i] += delta.rewards;
state.balances[i] = state.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.
/// For each attesting validator, reward the proposer who was first to include their attestation.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn get_proposer_deltas<T: EthSpec>(
deltas: &mut Vec<Delta>,
state: &mut BeaconState<T>,
state: &BeaconState<T>,
validator_statuses: &mut ValidatorStatuses,
winning_root_for_shards: &WinningRootHashSet,
spec: &ChainSpec,
@ -90,9 +85,7 @@ fn get_proposer_deltas<T: EthSpec>(
// 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();
for validator in &validator_statuses.statuses {
if validator.is_previous_epoch_attester {
let inclusion = validator
.inclusion_info
@ -101,7 +94,7 @@ fn get_proposer_deltas<T: EthSpec>(
let base_reward = get_base_reward(
state,
inclusion.proposer_index,
validator_statuses.total_balances.previous_epoch,
validator_statuses.total_balances.current_epoch,
spec,
)?;
@ -109,10 +102,8 @@ fn get_proposer_deltas<T: EthSpec>(
return Err(Error::ValidatorStatusesInconsistent);
}
delta.reward(base_reward / spec.attestation_inclusion_reward_quotient);
deltas[inclusion.proposer_index].reward(base_reward / spec.proposer_reward_quotient);
}
deltas[index] += delta;
}
Ok(())
@ -120,40 +111,30 @@ fn get_proposer_deltas<T: EthSpec>(
/// Apply rewards for participation in attestations during the previous epoch.
///
/// Spec v0.5.1
fn get_justification_and_finalization_deltas<T: EthSpec>(
/// Spec v0.6.1
fn get_attestation_deltas<T: EthSpec>(
deltas: &mut Vec<Delta>,
state: &BeaconState<T>,
validator_statuses: &ValidatorStatuses,
spec: &ChainSpec,
) -> Result<(), Error> {
let epochs_since_finality = epochs_since_finality(state, spec);
let finality_delay = (state.previous_epoch() - state.finalized_epoch).as_u64();
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,
validator_statuses.total_balances.current_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)
};
let delta = get_attestation_delta(
&validator,
&validator_statuses.total_balances,
base_reward,
finality_delay,
spec,
);
deltas[index] += delta;
}
@ -161,51 +142,79 @@ fn get_justification_and_finalization_deltas<T: EthSpec>(
Ok(())
}
/// Determine the delta for a single validator, if the chain is finalizing normally.
/// Determine the delta for a single validator, sans proposer rewards.
///
/// Spec v0.5.1
fn compute_normal_justification_and_finalization_delta(
/// Spec v0.6.1
fn get_attestation_delta(
validator: &ValidatorStatus,
total_balances: &TotalBalances,
base_reward: u64,
finality_delay: 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;
// Is this validator eligible to be rewarded or penalized?
// Spec: validator index in `eligible_validator_indices`
let is_eligible = validator.is_active_in_previous_epoch
|| (validator.is_slashed && !validator.is_withdrawable_in_current_epoch);
if !is_eligible {
return delta;
}
let total_balance = total_balances.current_epoch;
let total_attesting_balance = total_balances.previous_epoch_attesters;
let matching_head_balance = total_balances.previous_epoch_boundary_attesters;
let matching_target_balance = total_balances.previous_epoch_target_attesters;
let matching_head_balance = total_balances.previous_epoch_head_attesters;
// Expected FFG source.
if validator.is_previous_epoch_attester {
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_source_attestations)`
if validator.is_previous_epoch_attester && !validator.is_slashed {
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.reward(base_reward * spec.min_attestation_inclusion_delay / inclusion.distance);
} else {
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 {
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_target_attestations)`
if validator.is_previous_epoch_target_attester && !validator.is_slashed {
delta.reward(base_reward * matching_target_balance / total_balance);
} else {
delta.penalize(base_reward);
}
// Expected head.
if validator.is_previous_epoch_head_attester {
// Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_head_attestations)`
if validator.is_previous_epoch_head_attester && !validator.is_slashed {
delta.reward(base_reward * matching_head_balance / total_balance);
} else if validator.is_active_in_previous_epoch {
} else {
delta.penalize(base_reward);
};
}
// Proposer bonus is handled in `apply_proposer_deltas`.
// Inactivity penalty
if finality_delay > spec.min_epochs_to_inactivity_penalty {
// All eligible validators are penalized
delta.penalize(spec.base_rewards_per_epoch * base_reward);
// Additionally, all validators whose FFG target didn't match are penalized extra
if !validator.is_previous_epoch_target_attester {
delta.penalize(
validator.current_epoch_effective_balance * finality_delay
/ spec.inactivity_penalty_quotient,
);
}
}
// Proposer bonus is handled in `get_proposer_deltas`.
//
// This function only computes the delta for a single validator, so it cannot also return a
// delta for a validator.
@ -213,55 +222,9 @@ fn compute_normal_justification_and_finalization_delta(
delta
}
/// Determine the delta for a single delta, assuming the chain is _not_ finalizing normally.
///
/// Spec v0.5.1
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.1
/// Spec v0.6.1
fn get_crosslink_deltas<T: EthSpec>(
deltas: &mut Vec<Delta>,
state: &BeaconState<T>,
@ -274,7 +237,7 @@ fn get_crosslink_deltas<T: EthSpec>(
let base_reward = get_base_reward(
state,
index,
validator_statuses.total_balances.previous_epoch,
validator_statuses.total_balances.current_epoch,
spec,
)?;
@ -295,40 +258,20 @@ fn get_crosslink_deltas<T: EthSpec>(
/// Returns the base reward for some validator.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn get_base_reward<T: EthSpec>(
state: &BeaconState<T>,
index: usize,
previous_total_balance: u64,
// Should be == get_total_active_balance(state, spec)
total_active_balance: u64,
spec: &ChainSpec,
) -> Result<u64, BeaconStateError> {
if previous_total_balance == 0 {
if total_active_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)
let adjusted_quotient = total_active_balance.integer_sqrt() / spec.base_reward_quotient;
Ok(state.get_effective_balance(index, spec)?
/ adjusted_quotient
/ spec.base_rewards_per_epoch)
}
}
/// Returns the inactivity penalty for some validator.
///
/// Spec v0.5.1
fn get_inactivity_penalty<T: EthSpec>(
state: &BeaconState<T>,
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.1
fn epochs_since_finality<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> Epoch {
state.current_epoch(spec) + 1 - state.finalized_epoch
}

View File

@ -1,38 +0,0 @@
use crate::common::verify_bitfield_length;
use types::*;
/// Returns validator indices which participated in the attestation.
///
/// Spec v0.5.1
pub fn get_attestation_participants<T: EthSpec>(
state: &BeaconState<T>,
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)
}

View File

@ -1,56 +0,0 @@
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.
///
/// Spec v0.5.1
pub fn inclusion_distance<T: EthSpec>(
state: &BeaconState<T>,
attestations: &[&PendingAttestation],
validator_index: usize,
spec: &ChainSpec,
) -> Result<u64, InclusionError> {
let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?;
Ok((attestation.inclusion_slot - attestation.data.slot).as_u64())
}
/// Returns the slot of the earliest included attestation for some validator.
///
/// Spec v0.5.1
pub fn inclusion_slot<T: EthSpec>(
state: &BeaconState<T>,
attestations: &[&PendingAttestation],
validator_index: usize,
spec: &ChainSpec,
) -> Result<Slot, InclusionError> {
let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?;
Ok(attestation.inclusion_slot)
}
/// Finds the earliest included attestation for some validator.
///
/// Spec v0.5.1
fn earliest_included_attestation<T: EthSpec>(
state: &BeaconState<T>,
attestations: &[&PendingAttestation],
validator_index: usize,
spec: &ChainSpec,
) -> Result<PendingAttestation, InclusionError> {
let mut included_attestations = vec![];
for (i, a) in attestations.iter().enumerate() {
let participants =
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
if participants.iter().any(|i| *i == validator_index) {
included_attestations.push(i);
}
}
let earliest_attestation_index = included_attestations
.iter()
.min_by_key(|i| attestations[**i].inclusion_slot)
.ok_or_else(|| InclusionError::NoAttestationsForValidator)?;
Ok(attestations[*earliest_attestation_index].clone())
}

View File

@ -1,31 +0,0 @@
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.1
pub fn process_ejections<T: EthSpec>(
state: &mut BeaconState<T>,
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(())
}

View File

@ -1,42 +0,0 @@
use types::*;
/// Process the exit queue.
///
/// Spec v0.5.1
pub fn process_exit_queue<T: EthSpec>(state: &mut BeaconState<T>, 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.1
fn prepare_validator_for_withdrawal<T: EthSpec>(
state: &mut BeaconState<T>,
validator_index: usize,
spec: &ChainSpec,
) {
state.validator_registry[validator_index].withdrawable_epoch =
state.current_epoch(spec) + spec.min_validator_withdrawability_delay;
}

View File

@ -2,13 +2,13 @@ use types::{BeaconStateError as Error, *};
/// Process slashings.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
current_total_balance: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec);
let current_epoch = state.current_epoch();
let total_at_start = state.get_slashed_balance(current_epoch + 1)?;
let total_at_end = state.get_slashed_balance(current_epoch)?;
@ -24,10 +24,10 @@ pub fn process_slashings<T: EthSpec>(
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,
effective_balance / spec.min_slashing_penalty_quotient,
);
state.validator_balances[index] -= penalty;
safe_sub_assign!(state.balances[index], penalty);
}
}

View File

@ -0,0 +1,69 @@
use super::super::common::initiate_validator_exit;
use super::Error;
use itertools::{Either, Itertools};
use types::*;
/// Peforms a validator registry update, if required.
///
/// Spec v0.6.1
pub fn process_registry_updates<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
// Process activation eligibility and ejections.
// Collect eligible and exiting validators (we need to avoid mutating the state while iterating).
// We assume it's safe to re-order the change in eligibility and `initiate_validator_exit`.
// Rest assured exiting validators will still be exited in the same order as in the spec.
let current_epoch = state.current_epoch();
let is_eligible = |validator: &Validator| {
validator.activation_eligibility_epoch == spec.far_future_epoch
&& validator.effective_balance >= spec.max_effective_balance
};
let is_exiting_validator = |validator: &Validator| {
validator.is_active_at(current_epoch)
&& validator.effective_balance <= spec.ejection_balance
};
let (eligible_validators, exiting_validators): (Vec<_>, Vec<_>) = state
.validator_registry
.iter()
.enumerate()
.filter(|(_, validator)| is_eligible(validator) || is_exiting_validator(validator))
.partition_map(|(index, validator)| {
if is_eligible(validator) {
Either::Left(index)
} else {
Either::Right(index)
}
});
for index in eligible_validators {
state.validator_registry[index].activation_eligibility_epoch = current_epoch;
}
for index in exiting_validators {
initiate_validator_exit(state, index, spec)?;
}
// Queue validators eligible for activation and not dequeued for activation prior to finalized epoch
let activation_queue = state
.validator_registry
.iter()
.enumerate()
.filter(|(_, validator)| {
validator.activation_eligibility_epoch != spec.far_future_epoch
&& validator.activation_epoch
>= state.get_delayed_activation_exit_epoch(state.finalized_epoch, spec)
})
.sorted_by_key(|(_, validator)| validator.activation_eligibility_epoch)
.map(|(index, _)| index)
.collect_vec();
let churn_limit = state.get_churn_limit(spec)? as usize;
let delayed_activation_epoch = state.get_delayed_activation_exit_epoch(current_epoch, spec);
for index in activation_queue.into_iter().take(churn_limit) {
let validator = &mut state.validator_registry[index];
if validator.activation_epoch == spec.far_future_epoch {
validator.activation_epoch = delayed_activation_epoch;
}
}
Ok(())
}

View File

@ -1,5 +1,5 @@
#![cfg(test)]
use crate::per_epoch_processing;
use crate::per_epoch_processing::per_epoch_processing;
use env_logger::{Builder, Env};
use types::test_utils::TestingBeaconStateBuilder;
use types::*;

View File

@ -1,150 +0,0 @@
use super::super::common::exit_validator;
use super::Error;
use types::*;
/// Peforms a validator registry update, if required.
///
/// Spec v0.5.1
pub fn update_registry_and_shuffling_data<T: EthSpec>(
state: &mut BeaconState<T>,
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.1
pub fn should_update_validator_registry<T: EthSpec>(
state: &BeaconState<T>,
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.1
pub fn update_validator_registry<T: EthSpec>(
state: &mut BeaconState<T>,
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.1
pub fn activate_validator<T: EthSpec>(
state: &mut BeaconState<T>,
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)
}
}

View File

@ -1,5 +1,5 @@
use super::get_attestation_participants::get_attestation_participants;
use super::WinningRootHashSet;
use crate::common::get_attesting_indices_unsorted;
use types::*;
/// Sets the boolean `var` on `self` to be true if it is true on `other`. Otherwise leaves `self`
@ -29,7 +29,7 @@ pub struct InclusionInfo {
pub slot: Slot,
/// The distance between the attestation slot and the slot that attestation was included in a
/// block.
pub distance: Slot,
pub distance: u64,
/// The index of the proposer at the slot where the attestation was included.
pub proposer_index: usize,
}
@ -39,7 +39,7 @@ impl Default for InclusionInfo {
fn default() -> Self {
Self {
slot: Slot::max_value(),
distance: Slot::max_value(),
distance: u64::max_value(),
proposer_index: 0,
}
}
@ -68,17 +68,19 @@ pub struct ValidatorStatus {
pub is_active_in_current_epoch: bool,
/// True if the validator was active in the state's _previous_ epoch.
pub is_active_in_previous_epoch: bool,
/// The validator's effective balance in the _current_ epoch.
pub current_epoch_effective_balance: u64,
/// True if the validator had an attestation included in the _current_ epoch.
pub is_current_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _current_
/// epoch matches the block root known to the state.
pub is_current_epoch_boundary_attester: bool,
pub is_current_epoch_target_attester: bool,
/// True if the validator had an attestation included in the _previous_ epoch.
pub is_previous_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _previous_
/// epoch matches the block root known to the state.
pub is_previous_epoch_boundary_attester: bool,
pub is_previous_epoch_target_attester: bool,
/// True if the validator's beacon block root attestation in the _previous_ epoch at the
/// attestation's slot (`attestation_data.slot`) matches the block root known to the state.
pub is_previous_epoch_head_attester: bool,
@ -106,9 +108,9 @@ impl ValidatorStatus {
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);
set_self_if_other_is_true!(self, other, is_current_epoch_boundary_attester);
set_self_if_other_is_true!(self, other, is_current_epoch_target_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_boundary_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_target_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester);
if let Some(other_info) = other.inclusion_info {
@ -133,12 +135,12 @@ pub struct TotalBalances {
pub current_epoch_attesters: u64,
/// The total effective balance of all validators who attested during the _current_ epoch and
/// agreed with the state about the beacon block at the first slot of the _current_ epoch.
pub current_epoch_boundary_attesters: u64,
pub current_epoch_target_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch.
pub previous_epoch_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the first slot of the _previous_ epoch.
pub previous_epoch_boundary_attesters: u64,
pub previous_epoch_target_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the time of attestation.
pub previous_epoch_head_attesters: u64,
@ -160,7 +162,7 @@ impl ValidatorStatuses {
/// - Active validators
/// - Total balances for the current and previous epochs.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn new<T: EthSpec>(
state: &BeaconState<T>,
spec: &ChainSpec,
@ -169,21 +171,23 @@ impl ValidatorStatuses {
let mut total_balances = TotalBalances::default();
for (i, validator) in state.validator_registry.iter().enumerate() {
let effective_balance = state.get_effective_balance(i, spec)?;
let mut status = ValidatorStatus {
is_slashed: validator.slashed,
is_withdrawable_in_current_epoch: validator
.is_withdrawable_at(state.current_epoch(spec)),
.is_withdrawable_at(state.current_epoch()),
current_epoch_effective_balance: effective_balance,
..ValidatorStatus::default()
};
if validator.is_active_at(state.current_epoch(spec)) {
if validator.is_active_at(state.current_epoch()) {
status.is_active_in_current_epoch = true;
total_balances.current_epoch += state.get_effective_balance(i, spec)?;
total_balances.current_epoch += effective_balance;
}
if validator.is_active_at(state.previous_epoch(spec)) {
if validator.is_active_at(state.previous_epoch()) {
status.is_active_in_previous_epoch = true;
total_balances.previous_epoch += state.get_effective_balance(i, spec)?;
total_balances.previous_epoch += effective_balance;
}
statuses.push(status);
@ -198,7 +202,7 @@ impl ValidatorStatuses {
/// Process some attestations from the given `state` updating the `statuses` and
/// `total_balances` fields.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn process_attestations<T: EthSpec>(
&mut self,
state: &BeaconState<T>,
@ -210,44 +214,41 @@ impl ValidatorStatuses {
.chain(state.current_epoch_attestations.iter())
{
let attesting_indices =
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
let attesting_balance = state.get_total_balance(&attesting_indices, spec)?;
get_attesting_indices_unsorted(state, &a.data, &a.aggregation_bitfield)?;
let mut status = ValidatorStatus::default();
// Profile this attestation, updating the total balances and generating an
// `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;
if is_from_epoch(a, state.current_epoch()) {
status.is_current_epoch_attester = true;
if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? {
self.total_balances.current_epoch_boundary_attesters += attesting_balance;
status.is_current_epoch_boundary_attester = true;
if target_matches_epoch_start_block(a, state, state.current_epoch(), spec)? {
status.is_current_epoch_target_attester = true;
}
} else if is_from_epoch(a, state.previous_epoch(spec), spec) {
self.total_balances.previous_epoch_attesters += attesting_balance;
} else if is_from_epoch(a, state.previous_epoch()) {
status.is_previous_epoch_attester = true;
// The inclusion slot and distance are only required for previous epoch attesters.
let relative_epoch = RelativeEpoch::from_slot(state.slot, a.inclusion_slot, spec)?;
let attestation_slot = state.get_attestation_slot(&a.data)?;
let inclusion_slot = attestation_slot + a.inclusion_delay;
let relative_epoch =
RelativeEpoch::from_slot(state.slot, inclusion_slot, spec.slots_per_epoch)?;
status.inclusion_info = Some(InclusionInfo {
slot: a.inclusion_slot,
distance: inclusion_distance(a),
slot: inclusion_slot,
distance: a.inclusion_delay,
proposer_index: state.get_beacon_proposer_index(
a.inclusion_slot,
attestation_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;
status.is_previous_epoch_boundary_attester = true;
if target_matches_epoch_start_block(a, state, state.previous_epoch(), spec)? {
status.is_previous_epoch_target_attester = true;
}
if has_common_beacon_block_root(a, state)? {
self.total_balances.previous_epoch_head_attesters += attesting_balance;
status.is_previous_epoch_head_attester = true;
}
}
@ -258,13 +259,37 @@ impl ValidatorStatuses {
}
}
// Compute the total balances
for (index, v) in self.statuses.iter().enumerate() {
// According to the spec, we only count unslashed validators towards the totals.
if !v.is_slashed {
let validator_balance = state.get_effective_balance(index, spec)?;
if v.is_current_epoch_attester {
self.total_balances.current_epoch_attesters += validator_balance;
}
if v.is_current_epoch_target_attester {
self.total_balances.current_epoch_target_attesters += validator_balance;
}
if v.is_previous_epoch_attester {
self.total_balances.previous_epoch_attesters += validator_balance;
}
if v.is_previous_epoch_target_attester {
self.total_balances.previous_epoch_target_attesters += validator_balance;
}
if v.is_previous_epoch_head_attester {
self.total_balances.previous_epoch_head_attesters += validator_balance;
}
}
}
Ok(())
}
/// 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.5.1
/// Spec v0.6.1
pub fn process_winning_roots<T: EthSpec>(
&mut self,
state: &BeaconState<T>,
@ -272,9 +297,8 @@ impl ValidatorStatuses {
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
// Loop through each slot in the previous epoch.
for slot in state.previous_epoch(spec).slot_iter(spec.slots_per_epoch) {
let crosslink_committees_at_slot =
state.get_crosslink_committees_at_slot(slot, spec)?;
for slot in state.previous_epoch().slot_iter(spec.slots_per_epoch) {
let crosslink_committees_at_slot = state.get_crosslink_committees_at_slot(slot)?;
// Loop through each committee in the slot.
for c in crosslink_committees_at_slot {
@ -297,26 +321,18 @@ impl ValidatorStatuses {
}
}
/// Returns the distance between when the attestation was created and when it was included in a
/// block.
///
/// Spec v0.5.1
fn inclusion_distance(a: &PendingAttestation) -> Slot {
a.inclusion_slot - a.data.slot
}
/// Returns `true` if some `PendingAttestation` is from the supplied `epoch`.
///
/// Spec v0.5.1
fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool {
a.data.slot.epoch(spec.slots_per_epoch) == epoch
/// Spec v0.6.1
fn is_from_epoch(a: &PendingAttestation, epoch: Epoch) -> bool {
a.data.target_epoch == epoch
}
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
/// the first slot of the given epoch.
/// Returns `true` if the attestation's FFG target is equal to the hash of the `state`'s first
/// beacon block in the given `epoch`.
///
/// Spec v0.5.1
fn has_common_epoch_boundary_root<T: EthSpec>(
/// Spec v0.6.1
fn target_matches_epoch_start_block<T: EthSpec>(
a: &PendingAttestation,
state: &BeaconState<T>,
epoch: Epoch,
@ -331,12 +347,13 @@ fn has_common_epoch_boundary_root<T: EthSpec>(
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
/// the current slot of the `PendingAttestation`.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn has_common_beacon_block_root<T: EthSpec>(
a: &PendingAttestation,
state: &BeaconState<T>,
) -> Result<bool, BeaconStateError> {
let state_block_root = *state.get_block_root(a.data.slot)?;
let attestation_slot = state.get_attestation_slot(&a.data)?;
let state_block_root = *state.get_block_root(attestation_slot)?;
Ok(a.data.beacon_block_root == state_block_root)
}

View File

@ -1,11 +1,11 @@
use super::get_attestation_participants::get_attestation_participants;
use std::collections::HashSet;
use std::iter::FromIterator;
use crate::common::get_attesting_indices_unsorted;
use std::collections::{HashMap, HashSet};
use tree_hash::TreeHash;
use types::*;
#[derive(Clone)]
pub struct WinningRoot {
pub crosslink_data_root: Hash256,
pub crosslink: Crosslink,
pub attesting_validator_indices: Vec<usize>,
pub total_attesting_balance: u64,
}
@ -16,15 +16,15 @@ impl WinningRoot {
/// A winning root is "better" than another if it has a higher `total_attesting_balance`. Ties
/// are broken by favouring the higher `crosslink_data_root` value.
///
/// Spec v0.5.1
/// Spec v0.6.1
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
} else {
false
}
(
self.total_attesting_balance,
self.crosslink.crosslink_data_root,
) > (
other.total_attesting_balance,
other.crosslink.crosslink_data_root,
)
}
}
@ -34,43 +34,56 @@ impl WinningRoot {
/// The `WinningRoot` object also contains additional fields that are useful in later stages of
/// per-epoch processing.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn winning_root<T: EthSpec>(
state: &BeaconState<T>,
shard: u64,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<Option<WinningRoot>, BeaconStateError> {
let mut winning_root: Option<WinningRoot> = None;
let shard_attestations: Vec<&PendingAttestation> = state
.get_matching_source_attestations(epoch)?
.iter()
.filter(|a| a.data.shard == shard)
.collect();
let crosslink_data_roots: HashSet<Hash256> = HashSet::from_iter(
state
.previous_epoch_attestations
.iter()
.chain(state.current_epoch_attestations.iter())
.filter_map(|a| {
if is_eligible_for_winning_root(state, a, shard) {
Some(a.data.crosslink_data_root)
} else {
None
}
}),
);
let mut shard_crosslinks = Vec::with_capacity(shard_attestations.len());
for att in shard_attestations {
shard_crosslinks.push((
att,
state.get_crosslink_from_attestation_data(&att.data, spec)?,
));
}
for crosslink_data_root in crosslink_data_roots {
let current_shard_crosslink_root = state.get_current_crosslink(shard)?.tree_hash_root();
let candidate_crosslinks = shard_crosslinks.into_iter().filter(|(_, c)| {
c.previous_crosslink_root.as_bytes() == &current_shard_crosslink_root[..]
|| c.tree_hash_root() == current_shard_crosslink_root
});
// Build a map from candidate crosslink to attestations that support that crosslink.
let mut candidate_crosslink_map: HashMap<Crosslink, Vec<&PendingAttestation>> = HashMap::new();
for (attestation, crosslink) in candidate_crosslinks {
let supporting_attestations = candidate_crosslink_map
.entry(crosslink)
.or_insert_with(Vec::new);
supporting_attestations.push(attestation);
}
if candidate_crosslink_map.is_empty() {
return Ok(None);
}
let mut winning_root = None;
for (crosslink, attestations) in candidate_crosslink_map {
let attesting_validator_indices =
get_attesting_validator_indices(state, shard, &crosslink_data_root, 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))
})?;
get_unslashed_attesting_indices_unsorted(state, &attestations)?;
let total_attesting_balance =
state.get_total_balance(&attesting_validator_indices, spec)?;
let candidate = WinningRoot {
crosslink_data_root,
crosslink,
attesting_validator_indices,
total_attesting_balance,
};
@ -87,56 +100,27 @@ pub fn winning_root<T: EthSpec>(
Ok(winning_root)
}
/// Returns `true` if pending attestation `a` is eligible to become a winning root.
///
/// Spec v0.5.1
fn is_eligible_for_winning_root<T: EthSpec>(
pub fn get_unslashed_attesting_indices_unsorted<T: EthSpec>(
state: &BeaconState<T>,
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.1
fn get_attesting_validator_indices<T: EthSpec>(
state: &BeaconState<T>,
shard: u64,
crosslink_data_root: &Hash256,
spec: &ChainSpec,
attestations: &[&PendingAttestation],
) -> Result<Vec<usize>, BeaconStateError> {
let mut indices = vec![];
for a in state
.current_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 get_attestation_participants(
state,
&a.data,
&a.aggregation_bitfield,
spec,
)?);
}
let mut output = HashSet::new();
for a in attestations {
output.extend(get_attesting_indices_unsorted(
state,
&a.data,
&a.aggregation_bitfield,
)?);
}
// 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)
Ok(output
.into_iter()
.filter(|index| {
state
.validator_registry
.get(*index)
.map_or(false, |v| !v.slashed)
})
.collect())
}
#[cfg(test)]
@ -146,15 +130,17 @@ mod tests {
#[test]
fn is_better_than() {
let worse = WinningRoot {
crosslink_data_root: Hash256::from_slice(&[1; 32]),
crosslink: Crosslink {
epoch: Epoch::new(0),
previous_crosslink_root: Hash256::from_slice(&[0; 32]),
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()
};
let mut better = worse.clone();
better.crosslink.crosslink_data_root = Hash256::from_slice(&[2; 32]);
assert!(better.is_better_than(&worse));

View File

@ -10,14 +10,14 @@ pub enum Error {
/// Advances a state forward by one slot, performing per-epoch processing if required.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn per_slot_processing<T: EthSpec>(
state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
cache_state(state, spec)?;
if (state.slot + 1) % spec.slots_per_epoch == 0 {
if (state.slot > spec.genesis_slot) && ((state.slot + 1) % spec.slots_per_epoch == 0) {
per_epoch_processing(state, spec)?;
}

View File

@ -8,6 +8,8 @@ edition = "2018"
bls = { path = "../utils/bls" }
boolean-bitfield = { path = "../utils/boolean-bitfield" }
cached_tree_hash = { path = "../utils/cached_tree_hash" }
compare_fields = { path = "../utils/compare_fields" }
compare_fields_derive = { path = "../utils/compare_fields_derive" }
dirs = "1.0"
derivative = "1.0"
ethereum-types = "0.5"

View File

@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// Details an attestation that can be slashable.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
Clone,
@ -28,7 +28,7 @@ pub struct Attestation {
pub data: AttestationData,
pub custody_bitfield: Bitfield,
#[signed_root(skip_hashing)]
pub aggregate_signature: AggregateSignature,
pub signature: AggregateSignature,
}
impl Attestation {
@ -49,8 +49,7 @@ impl Attestation {
self.aggregation_bitfield
.union_inplace(&other.aggregation_bitfield);
self.custody_bitfield.union_inplace(&other.custody_bitfield);
self.aggregate_signature
.add_aggregate(&other.aggregate_signature);
self.signature.add_aggregate(&other.signature);
}
}

View File

@ -1,5 +1,5 @@
use crate::test_utils::TestRandom;
use crate::{Crosslink, Epoch, Hash256, Slot};
use crate::{Epoch, Hash256};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@ -9,11 +9,12 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// The data upon which an attestation is based.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Default,
Serialize,
Deserialize,
@ -27,22 +28,20 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
)]
pub struct AttestationData {
// LMD GHOST vote
pub slot: Slot,
pub beacon_block_root: Hash256,
// FFG Vote
pub source_epoch: Epoch,
pub source_root: Hash256,
pub target_epoch: Epoch,
pub target_root: Hash256,
// Crosslink Vote
pub shard: u64,
pub previous_crosslink: Crosslink,
pub previous_crosslink_root: Hash256,
pub crosslink_data_root: Hash256,
}
impl Eq for AttestationData {}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -1,29 +1,31 @@
use super::AttestationData;
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash_derive::{CachedTreeHash, TreeHash};
/// Used for pairing an attestation with a proof-of-custody.
///
/// Spec v0.5.1
#[derive(Debug, Clone, PartialEq, Default, Serialize, Encode, Decode, TreeHash, CachedTreeHash)]
/// Spec v0.6.1
#[derive(
Debug,
Clone,
PartialEq,
Default,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
CachedTreeHash,
TestRandom,
)]
pub struct AttestationDataAndCustodyBit {
pub data: AttestationData,
pub custody_bit: bool,
}
impl TestRandom for AttestationDataAndCustodyBit {
fn random_for_test(rng: &mut impl RngCore) -> Self {
Self {
data: <_>::random_for_test(rng),
custody_bit: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -1,4 +1,4 @@
use crate::{test_utils::TestRandom, SlashableAttestation};
use crate::{test_utils::TestRandom, IndexedAttestation};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@ -7,7 +7,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// Two conflicting attestations.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
@ -21,8 +21,8 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
TestRandom,
)]
pub struct AttesterSlashing {
pub slashable_attestation_1: SlashableAttestation,
pub slashable_attestation_2: SlashableAttestation,
pub attestation_1: IndexedAttestation,
pub attestation_2: IndexedAttestation,
}
#[cfg(test)]

View File

@ -10,7 +10,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// A block of the `BeaconChain`.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
@ -36,18 +36,20 @@ pub struct BeaconBlock {
impl BeaconBlock {
/// Returns an empty block to be used during genesis.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn empty(spec: &ChainSpec) -> BeaconBlock {
BeaconBlock {
slot: spec.genesis_slot,
previous_block_root: spec.zero_hash,
state_root: spec.zero_hash,
body: BeaconBlockBody {
randao_reveal: spec.empty_signature.clone(),
randao_reveal: Signature::empty_signature(),
eth1_data: Eth1Data {
deposit_root: spec.zero_hash,
block_hash: spec.zero_hash,
deposit_count: 0,
},
graffiti: [0; 32],
proposer_slashings: vec![],
attester_slashings: vec![],
attestations: vec![],
@ -55,13 +57,13 @@ impl BeaconBlock {
voluntary_exits: vec![],
transfers: vec![],
},
signature: spec.empty_signature.clone(),
signature: Signature::empty_signature(),
}
}
/// Returns the `tree_hash_root | update` of the block.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn canonical_root(&self) -> Hash256 {
Hash256::from_slice(&self.tree_hash_root()[..])
}
@ -73,7 +75,7 @@ impl BeaconBlock {
///
/// Note: performs a full tree-hash of `self.body`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn block_header(&self) -> BeaconBlockHeader {
BeaconBlockHeader {
slot: self.slot,
@ -86,11 +88,11 @@ impl BeaconBlock {
/// Returns a "temporary" header, where the `state_root` is `spec.zero_hash`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn temporary_block_header(&self, spec: &ChainSpec) -> BeaconBlockHeader {
BeaconBlockHeader {
state_root: spec.zero_hash,
signature: spec.empty_signature.clone(),
signature: Signature::empty_signature(),
..self.block_header()
}
}

View File

@ -1,4 +1,4 @@
use crate::test_utils::TestRandom;
use crate::test_utils::{graffiti_from_hex_str, TestRandom};
use crate::*;
use serde_derive::{Deserialize, Serialize};
@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// The body of a `BeaconChain` block, containing operations.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
@ -24,6 +24,8 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
pub struct BeaconBlockBody {
pub randao_reveal: Signature,
pub eth1_data: Eth1Data,
#[serde(deserialize_with = "graffiti_from_hex_str")]
pub graffiti: [u8; 32],
pub proposer_slashings: Vec<ProposerSlashing>,
pub attester_slashings: Vec<AttesterSlashing>,
pub attestations: Vec<Attestation>,

View File

@ -10,7 +10,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// A header of a `BeaconBlock`.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
@ -36,14 +36,14 @@ pub struct BeaconBlockHeader {
impl BeaconBlockHeader {
/// Returns the `tree_hash_root` of the header.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn canonical_root(&self) -> Hash256 {
Hash256::from_slice(&self.signed_root()[..])
}
/// Given a `body`, consumes `self` and returns a complete `BeaconBlock`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn into_block(self, body: BeaconBlockBody) -> BeaconBlock {
BeaconBlock {
slot: self.slot,

View File

@ -1,12 +1,13 @@
use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError};
use self::committee_cache::{get_active_validator_indices, CommitteeCache};
use self::exit_cache::ExitCache;
use crate::test_utils::TestRandom;
use crate::*;
use cached_tree_hash::{Error as TreeHashCacheError, TreeHashCache};
use hashing::hash;
use int_to_bytes::int_to_bytes32;
use pubkey_cache::PubkeyCache;
use compare_fields_derive::CompareFields;
use fixed_len_vec::{typenum::Unsigned, FixedLenVec};
use hashing::hash;
use int_to_bytes::{int_to_bytes32, int_to_bytes8};
use pubkey_cache::PubkeyCache;
use serde_derive::{Deserialize, Serialize};
use ssz::ssz_encode;
use ssz_derive::{Decode, Encode};
@ -17,11 +18,13 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
pub use beacon_state_types::*;
mod beacon_state_types;
mod epoch_cache;
mod committee_cache;
mod exit_cache;
mod pubkey_cache;
mod tests;
pub const CACHED_EPOCHS: usize = 4;
pub const CACHED_EPOCHS: usize = 3;
const MAX_RANDOM_BYTE: u64 = (1 << 8) - 1;
#[derive(Debug, PartialEq)]
pub enum Error {
@ -32,6 +35,9 @@ pub enum Error {
UnableToDetermineProducer,
InvalidBitfield,
ValidatorIsWithdrawable,
UnableToShuffle,
TooManyValidators,
InsufficientValidators,
InsufficientRandaoMixes,
InsufficientBlockRoots,
InsufficientIndexRoots,
@ -40,20 +46,23 @@ pub enum Error {
InsufficientSlashedBalances,
InsufficientStateRoots,
NoCommitteeForShard,
NoCommitteeForSlot,
ZeroSlotsPerEpoch,
PubkeyCacheInconsistent,
PubkeyCacheIncomplete {
cache_len: usize,
registry_len: usize,
},
EpochCacheUninitialized(RelativeEpoch),
PreviousCommitteeCacheUninitialized,
CurrentCommitteeCacheUninitialized,
RelativeEpochError(RelativeEpochError),
EpochCacheError(EpochCacheError),
CommitteeCacheUninitialized(RelativeEpoch),
TreeHashCacheError(TreeHashCacheError),
}
/// The state of the `BeaconChain` at some slot.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
@ -65,6 +74,7 @@ pub enum Error {
Decode,
TreeHash,
CachedTreeHash,
CompareFields,
)]
pub struct BeaconState<T>
where
@ -76,18 +86,14 @@ where
pub fork: Fork,
// Validator registry
#[compare_fields(as_slice)]
pub validator_registry: Vec<Validator>,
pub validator_balances: Vec<u64>,
pub validator_registry_update_epoch: Epoch,
#[compare_fields(as_slice)]
pub balances: Vec<u64>,
// Randomness and committees
pub latest_randao_mixes: FixedLenVec<Hash256, T::LatestRandaoMixesLength>,
pub previous_shuffling_start_shard: u64,
pub current_shuffling_start_shard: u64,
pub previous_shuffling_epoch: Epoch,
pub current_shuffling_epoch: Epoch,
pub previous_shuffling_seed: Hash256,
pub current_shuffling_seed: Hash256,
pub latest_start_shard: u64,
// Finality
pub previous_epoch_attestations: Vec<PendingAttestation>,
@ -101,7 +107,8 @@ where
pub finalized_root: Hash256,
// Recent state
pub latest_crosslinks: FixedLenVec<Crosslink, T::ShardCount>,
pub current_crosslinks: FixedLenVec<Crosslink, T::ShardCount>,
pub previous_crosslinks: FixedLenVec<Crosslink, T::ShardCount>,
pub latest_block_roots: FixedLenVec<Hash256, T::SlotsPerHistoricalRoot>,
latest_state_roots: FixedLenVec<Hash256, T::SlotsPerHistoricalRoot>,
latest_active_index_roots: FixedLenVec<Hash256, T::LatestActiveIndexRootsLength>,
@ -111,7 +118,7 @@ where
// Ethereum 1.0 chain data
pub latest_eth1_data: Eth1Data,
pub eth1_data_votes: Vec<Eth1DataVote>,
pub eth1_data_votes: Vec<Eth1Data>,
pub deposit_index: u64,
// Caching (not in the spec)
@ -120,13 +127,7 @@ where
#[ssz(skip_deserializing)]
#[tree_hash(skip_hashing)]
#[test_random(default)]
pub cache_index_offset: usize,
#[serde(default)]
#[ssz(skip_serializing)]
#[ssz(skip_deserializing)]
#[tree_hash(skip_hashing)]
#[test_random(default)]
pub caches: [EpochCache; CACHED_EPOCHS],
pub committee_caches: [CommitteeCache; CACHED_EPOCHS],
#[serde(default)]
#[ssz(skip_serializing)]
#[ssz(skip_deserializing)]
@ -139,6 +140,12 @@ where
#[tree_hash(skip_hashing)]
#[test_random(default)]
pub tree_hash_cache: TreeHashCache,
#[serde(skip_serializing, skip_deserializing)]
#[ssz(skip_serializing)]
#[ssz(skip_deserializing)]
#[tree_hash(skip_hashing)]
#[test_random(default)]
pub exit_cache: ExitCache,
}
impl<T: EthSpec> BeaconState<T> {
@ -147,7 +154,7 @@ impl<T: EthSpec> BeaconState<T> {
/// This does not fully build a genesis beacon state, it omits processing of initial validator
/// deposits. To obtain a full genesis beacon state, use the `BeaconStateBuilder`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn genesis(
genesis_time: u64,
latest_eth1_data: Eth1Data,
@ -155,6 +162,7 @@ impl<T: EthSpec> BeaconState<T> {
) -> BeaconState<T> {
let initial_crosslink = Crosslink {
epoch: spec.genesis_epoch,
previous_crosslink_root: spec.zero_hash,
crosslink_data_root: spec.zero_hash,
};
@ -166,20 +174,14 @@ impl<T: EthSpec> BeaconState<T> {
// Validator registry
validator_registry: vec![], // Set later in the function.
validator_balances: vec![], // Set later in the function.
validator_registry_update_epoch: spec.genesis_epoch,
balances: vec![], // Set later in the function.
// Randomness and committees
latest_randao_mixes: FixedLenVec::from(vec![
spec.zero_hash;
T::LatestRandaoMixesLength::to_usize()
]),
previous_shuffling_start_shard: spec.genesis_start_shard,
current_shuffling_start_shard: spec.genesis_start_shard,
previous_shuffling_epoch: spec.genesis_epoch,
current_shuffling_epoch: spec.genesis_epoch,
previous_shuffling_seed: spec.zero_hash,
current_shuffling_seed: spec.zero_hash,
latest_start_shard: 0,
// Finality
previous_epoch_attestations: vec![],
@ -193,22 +195,16 @@ impl<T: EthSpec> BeaconState<T> {
finalized_root: spec.zero_hash,
// Recent state
latest_crosslinks: vec![initial_crosslink; spec.shard_count as usize].into(),
latest_block_roots: FixedLenVec::from(vec![
current_crosslinks: vec![initial_crosslink.clone(); T::ShardCount::to_usize()].into(),
previous_crosslinks: vec![initial_crosslink; T::ShardCount::to_usize()].into(),
latest_block_roots: vec![spec.zero_hash; T::SlotsPerHistoricalRoot::to_usize()].into(),
latest_state_roots: vec![spec.zero_hash; T::SlotsPerHistoricalRoot::to_usize()].into(),
latest_active_index_roots: vec![
spec.zero_hash;
T::SlotsPerHistoricalRoot::to_usize()
]),
latest_state_roots: FixedLenVec::from(vec![
spec.zero_hash;
T::SlotsPerHistoricalRoot::to_usize()
]),
latest_active_index_roots: FixedLenVec::from(
vec![spec.zero_hash; T::LatestActiveIndexRootsLength::to_usize()],
),
latest_slashed_balances: FixedLenVec::from(vec![
0;
T::LatestSlashedExitLength::to_usize()
]),
T::LatestActiveIndexRootsLength::to_usize()
]
.into(),
latest_slashed_balances: vec![0; T::LatestSlashedExitLength::to_usize()].into(),
latest_block_header: BeaconBlock::empty(spec).temporary_block_header(spec),
historical_roots: vec![],
@ -222,21 +218,20 @@ impl<T: EthSpec> BeaconState<T> {
/*
* Caching (not in spec)
*/
cache_index_offset: 0,
caches: [
EpochCache::default(),
EpochCache::default(),
EpochCache::default(),
EpochCache::default(),
committee_caches: [
CommitteeCache::default(),
CommitteeCache::default(),
CommitteeCache::default(),
],
pubkey_cache: PubkeyCache::default(),
tree_hash_cache: TreeHashCache::default(),
exit_cache: ExitCache::default(),
}
}
/// Returns the `tree_hash_root` of the state.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn canonical_root(&self) -> Hash256 {
Hash256::from_slice(&self.tree_hash_root()[..])
}
@ -265,44 +260,80 @@ impl<T: EthSpec> BeaconState<T> {
/// The epoch corresponding to `self.slot`.
///
/// Spec v0.5.1
pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch {
self.slot.epoch(spec.slots_per_epoch)
/// Spec v0.6.1
pub fn current_epoch(&self) -> Epoch {
self.slot.epoch(T::slots_per_epoch())
}
/// The epoch prior to `self.current_epoch()`.
///
/// If the current epoch is the genesis epoch, the genesis_epoch is returned.
///
/// Spec v0.5.1
pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch {
self.current_epoch(&spec) - 1
/// Spec v0.6.1
pub fn previous_epoch(&self) -> Epoch {
let current_epoch = self.current_epoch();
if current_epoch > T::genesis_epoch() {
current_epoch - 1
} else {
current_epoch
}
}
/// The epoch following `self.current_epoch()`.
///
/// Spec v0.5.1
pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch {
self.current_epoch(spec) + 1
/// Spec v0.6.1
pub fn next_epoch(&self) -> Epoch {
self.current_epoch() + 1
}
/// Returns the active validator indices for the given epoch, assuming there is no validator
/// registry update in the next epoch.
///
/// This uses the cache, so it saves an iteration over the validator registry, however it can
/// not return a result for any epoch before the previous epoch.
pub fn get_epoch_committee_count(&self, relative_epoch: RelativeEpoch) -> Result<u64, Error> {
let cache = self.cache(relative_epoch)?;
Ok(cache.epoch_committee_count() as u64)
}
pub fn get_epoch_start_shard(&self, relative_epoch: RelativeEpoch) -> Result<u64, Error> {
let cache = self.cache(relative_epoch)?;
Ok(cache.epoch_start_shard())
}
pub fn next_epoch_start_shard(&self) -> Result<u64, Error> {
let cache = self.cache(RelativeEpoch::Current)?;
Ok(
(cache.epoch_start_shard() + cache.epoch_committee_count() as u64)
& T::shard_count() as u64,
)
}
/// Get the slot of an attestation.
///
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn get_attestation_slot(&self, attestation_data: &AttestationData) -> Result<Slot, Error> {
let target_relative_epoch =
RelativeEpoch::from_epoch(self.current_epoch(), attestation_data.target_epoch)?;
let cc =
self.get_crosslink_committee_for_shard(attestation_data.shard, target_relative_epoch)?;
Ok(cc.slot)
}
/// Return the cached active validator indices at some epoch.
///
/// Note: the indices are shuffled (i.e., not in ascending order).
///
/// Returns an error if that epoch is not cached, or the cache is not initialized.
pub fn get_cached_active_validator_indices(
&self,
relative_epoch: RelativeEpoch,
spec: &ChainSpec,
) -> Result<&[usize], Error> {
let cache = self.cache(relative_epoch, spec)?;
let cache = self.cache(relative_epoch)?;
Ok(&cache.active_validator_indices)
Ok(&cache.active_validator_indices())
}
/// Returns the active validator indices for the given epoch.
@ -314,6 +345,17 @@ impl<T: EthSpec> BeaconState<T> {
get_active_validator_indices(&self.validator_registry, epoch)
}
/// Return the cached active validator indices at some epoch.
///
/// Note: the indices are shuffled (i.e., not in ascending order).
///
/// Returns an error if that epoch is not cached, or the cache is not initialized.
pub fn get_shuffling(&self, relative_epoch: RelativeEpoch) -> Result<&[usize], Error> {
let cache = self.cache(relative_epoch)?;
Ok(cache.shuffling())
}
/// Returns the crosslink committees for some slot.
///
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
@ -322,78 +364,69 @@ impl<T: EthSpec> BeaconState<T> {
pub fn get_crosslink_committees_at_slot(
&self,
slot: Slot,
spec: &ChainSpec,
) -> Result<&Vec<CrosslinkCommittee>, Error> {
// If the slot is in the next epoch, assume there was no validator registry update.
let relative_epoch = match RelativeEpoch::from_slot(self.slot, slot, spec) {
Err(RelativeEpochError::AmbiguiousNextEpoch) => {
Ok(RelativeEpoch::NextWithoutRegistryChange)
}
e => e,
}?;
) -> Result<Vec<CrosslinkCommittee>, Error> {
let relative_epoch = RelativeEpoch::from_slot(self.slot, slot, T::slots_per_epoch())?;
let cache = self.cache(relative_epoch)?;
let cache = self.cache(relative_epoch, spec)?;
Ok(cache
.get_crosslink_committees_at_slot(slot, spec)
.ok_or_else(|| Error::SlotOutOfBounds)?)
cache
.get_crosslink_committees_for_slot(slot)
.ok_or_else(|| Error::NoCommitteeForSlot)
}
/// Returns the crosslink committees for some shard in an epoch.
/// Returns the crosslink committees for some shard in some cached epoch.
///
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn get_crosslink_committee_for_shard(
&self,
epoch: Epoch,
shard: Shard,
spec: &ChainSpec,
) -> Result<&CrosslinkCommittee, Error> {
// If the slot is in the next epoch, assume there was no validator registry update.
let relative_epoch = match RelativeEpoch::from_epoch(self.current_epoch(spec), epoch) {
Err(RelativeEpochError::AmbiguiousNextEpoch) => {
Ok(RelativeEpoch::NextWithoutRegistryChange)
}
e => e,
}?;
shard: u64,
relative_epoch: RelativeEpoch,
) -> Result<CrosslinkCommittee, Error> {
let cache = self.cache(relative_epoch)?;
let cache = self.cache(relative_epoch, spec)?;
let committee = cache
.get_crosslink_committee_for_shard(shard)
.ok_or_else(|| Error::NoCommitteeForShard)?;
Ok(cache
.get_crosslink_committee_for_shard(shard, spec)
.ok_or_else(|| Error::NoCommitteeForShard)?)
Ok(committee)
}
/// Returns the beacon proposer index for the `slot`.
/// Returns the beacon proposer index for the `slot` in the given `relative_epoch`.
///
/// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned.
///
/// Spec v0.5.1
/// Spec v0.6.1
// NOTE: be sure to test this bad boy.
pub fn get_beacon_proposer_index(
&self,
slot: Slot,
relative_epoch: RelativeEpoch,
spec: &ChainSpec,
) -> Result<usize, Error> {
let cache = self.cache(relative_epoch, spec)?;
let cache = self.cache(relative_epoch)?;
let epoch = relative_epoch.into_epoch(self.current_epoch());
let committees = cache
.get_crosslink_committees_at_slot(slot, spec)
let first_committee = cache
.first_committee_at_slot(slot)
.ok_or_else(|| Error::SlotOutOfBounds)?;
let seed = self.generate_seed(epoch, spec)?;
let epoch = slot.epoch(spec.slots_per_epoch);
committees
.first()
.ok_or(Error::UnableToDetermineProducer)
.and_then(|first| {
let index = epoch
.as_usize()
.checked_rem(first.committee.len())
.ok_or(Error::UnableToDetermineProducer)?;
Ok(first.committee[index])
})
let mut i = 0;
Ok(loop {
let candidate_index = first_committee[(epoch.as_usize() + i) % first_committee.len()];
let random_byte = {
let mut preimage = seed.as_bytes().to_vec();
preimage.append(&mut int_to_bytes8((i / 32) as u64));
let hash = hash(&preimage);
hash[i % 32]
};
let effective_balance = self.validator_registry[candidate_index].effective_balance;
if (effective_balance * MAX_RANDOM_BYTE)
>= (spec.max_effective_balance * random_byte as u64)
{
break candidate_index;
}
i += 1;
})
}
/// Safely obtains the index for latest block roots, given some `slot`.
@ -415,6 +448,18 @@ impl<T: EthSpec> BeaconState<T> {
Ok(&self.latest_block_roots[i])
}
/// Return the block root at a recent `slot`.
///
/// Spec v0.6.0
// FIXME(sproul): name swap with get_block_root
pub fn get_block_root_at_epoch(
&self,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<&Hash256, BeaconStateError> {
self.get_block_root(epoch.start_slot(spec.slots_per_epoch))
}
/// Sets the block root for some given slot.
///
/// Spec v0.5.1
@ -431,11 +476,11 @@ impl<T: EthSpec> BeaconState<T> {
/// Safely obtains the index for `latest_randao_mixes`
///
/// Spec v0.5.1
fn get_randao_mix_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result<usize, Error> {
let current_epoch = self.current_epoch(spec);
fn get_randao_mix_index(&self, epoch: Epoch) -> Result<usize, Error> {
let current_epoch = self.current_epoch();
let len = T::LatestRandaoMixesLength::to_u64();
if (current_epoch - len < epoch) & (epoch <= current_epoch) {
if (epoch + len > current_epoch) & (epoch <= current_epoch) {
Ok(epoch.as_usize() % len as usize)
} else {
Err(Error::EpochOutOfBounds)
@ -448,18 +493,13 @@ impl<T: EthSpec> BeaconState<T> {
///
/// See `Self::get_randao_mix`.
///
/// Spec v0.5.1
pub fn update_randao_mix(
&mut self,
epoch: Epoch,
signature: &Signature,
spec: &ChainSpec,
) -> Result<(), Error> {
/// Spec v0.6.1
pub fn update_randao_mix(&mut self, epoch: Epoch, signature: &Signature) -> Result<(), Error> {
let i = epoch.as_usize() % T::LatestRandaoMixesLength::to_usize();
let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature)));
self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash;
self.latest_randao_mixes[i] = *self.get_randao_mix(epoch)? ^ signature_hash;
Ok(())
}
@ -467,36 +507,30 @@ impl<T: EthSpec> BeaconState<T> {
/// Return the randao mix at a recent ``epoch``.
///
/// Spec v0.5.1
pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> {
let i = self.get_randao_mix_index(epoch, spec)?;
pub fn get_randao_mix(&self, epoch: Epoch) -> Result<&Hash256, Error> {
let i = self.get_randao_mix_index(epoch)?;
Ok(&self.latest_randao_mixes[i])
}
/// Set the randao mix at a recent ``epoch``.
///
/// Spec v0.5.1
pub fn set_randao_mix(
&mut self,
epoch: Epoch,
mix: Hash256,
spec: &ChainSpec,
) -> Result<(), Error> {
let i = self.get_randao_mix_index(epoch, spec)?;
pub fn set_randao_mix(&mut self, epoch: Epoch, mix: Hash256) -> Result<(), Error> {
let i = self.get_randao_mix_index(epoch)?;
self.latest_randao_mixes[i] = mix;
Ok(())
}
/// Safely obtains the index for `latest_active_index_roots`, given some `epoch`.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn get_active_index_root_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result<usize, Error> {
let current_epoch = self.current_epoch(spec);
let current_epoch = self.current_epoch();
if (current_epoch - self.latest_active_index_roots.len() as u64
+ spec.activation_exit_delay
< epoch)
& (epoch <= current_epoch + spec.activation_exit_delay)
{
let lookahead = spec.activation_exit_delay;
let lookback = self.latest_active_index_roots.len() as u64 - lookahead;
if (epoch + lookback > current_epoch) && (current_epoch + lookahead >= epoch) {
Ok(epoch.as_usize() % self.latest_active_index_roots.len())
} else {
Err(Error::EpochOutOfBounds)
@ -505,7 +539,7 @@ impl<T: EthSpec> BeaconState<T> {
/// Return the `active_index_root` at a recent `epoch`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Result<Hash256, Error> {
let i = self.get_active_index_root_index(epoch, spec)?;
Ok(self.latest_active_index_roots[i])
@ -513,7 +547,7 @@ impl<T: EthSpec> BeaconState<T> {
/// Set the `active_index_root` at a recent `epoch`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn set_active_index_root(
&mut self,
epoch: Epoch,
@ -527,10 +561,10 @@ impl<T: EthSpec> BeaconState<T> {
/// Replace `active_index_roots` with clones of `index_root`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn fill_active_index_roots_with(&mut self, index_root: Hash256) {
self.latest_active_index_roots =
vec![index_root; self.latest_active_index_roots.len() as usize].into()
vec![index_root; self.latest_active_index_roots.len()].into()
}
/// Safely obtains the index for latest state roots, given some `slot`.
@ -563,7 +597,7 @@ impl<T: EthSpec> BeaconState<T> {
/// Safely obtains the index for `latest_slashed_balances`, given some `epoch`.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn get_slashed_balance_index(&self, epoch: Epoch) -> Result<usize, Error> {
let i = epoch.as_usize() % self.latest_slashed_balances.len();
@ -578,7 +612,7 @@ impl<T: EthSpec> BeaconState<T> {
/// Gets the total slashed balances for some epoch.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn get_slashed_balance(&self, epoch: Epoch) -> Result<u64, Error> {
let i = self.get_slashed_balance_index(epoch)?;
Ok(self.latest_slashed_balances[i])
@ -586,42 +620,99 @@ impl<T: EthSpec> BeaconState<T> {
/// Sets the total slashed balances for some epoch.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn set_slashed_balance(&mut self, epoch: Epoch, balance: u64) -> Result<(), Error> {
let i = self.get_slashed_balance_index(epoch)?;
self.latest_slashed_balances[i] = balance;
Ok(())
}
/// Get the attestations from the current or previous epoch.
///
/// Spec v0.6.0
pub fn get_matching_source_attestations(
&self,
epoch: Epoch,
) -> Result<&[PendingAttestation], Error> {
if epoch == self.current_epoch() {
Ok(&self.current_epoch_attestations)
} else if epoch == self.previous_epoch() {
Ok(&self.previous_epoch_attestations)
} else {
Err(Error::EpochOutOfBounds)
}
}
/// Get the current crosslink for a shard.
///
/// Spec v0.6.1
pub fn get_current_crosslink(&self, shard: u64) -> Result<&Crosslink, Error> {
self.current_crosslinks
.get(shard as usize)
.ok_or(Error::ShardOutOfBounds)
}
/// Get the previous crosslink for a shard.
///
/// Spec v0.6.1
pub fn get_previous_crosslink(&self, shard: u64) -> Result<&Crosslink, Error> {
self.previous_crosslinks
.get(shard as usize)
.ok_or(Error::ShardOutOfBounds)
}
/// Transform an attestation into the crosslink that it reinforces.
///
/// Spec v0.6.1
pub fn get_crosslink_from_attestation_data(
&self,
data: &AttestationData,
spec: &ChainSpec,
) -> Result<Crosslink, Error> {
let current_crosslink_epoch = self.get_current_crosslink(data.shard)?.epoch;
Ok(Crosslink {
epoch: std::cmp::min(
data.target_epoch,
current_crosslink_epoch + spec.max_crosslink_epochs,
),
previous_crosslink_root: data.previous_crosslink_root,
crosslink_data_root: data.crosslink_data_root,
})
}
/// Generate a seed for the given `epoch`.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result<Hash256, Error> {
let mut input = self
.get_randao_mix(epoch - spec.min_seed_lookahead, spec)?
.as_bytes()
.to_vec();
// Bypass the safe getter for RANDAO so we can gracefully handle the scenario where `epoch
// == 0`.
let randao = {
let i = epoch + T::latest_randao_mixes_length() as u64 - spec.min_seed_lookahead;
self.latest_randao_mixes[i.as_usize() % self.latest_randao_mixes.len()]
};
let active_index_root = self.get_active_index_root(epoch, spec)?;
let epoch_bytes = int_to_bytes32(epoch.as_u64());
input.append(&mut self.get_active_index_root(epoch, spec)?.as_bytes().to_vec());
let mut preimage = [0; 32 * 3];
preimage[0..32].copy_from_slice(&randao[..]);
preimage[32..64].copy_from_slice(&active_index_root[..]);
preimage[64..].copy_from_slice(&epoch_bytes);
input.append(&mut int_to_bytes32(epoch.as_u64()));
Ok(Hash256::from_slice(&hash(&input[..])[..]))
Ok(Hash256::from_slice(&hash(&preimage)))
}
/// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``.
///
/// Spec v0.5.1
/// Spec v0.6.0
pub fn get_effective_balance(
&self,
validator_index: usize,
spec: &ChainSpec,
_spec: &ChainSpec,
) -> Result<u64, Error> {
let balance = self
.validator_balances
self.validator_registry
.get(validator_index)
.ok_or_else(|| Error::UnknownValidator)?;
Ok(std::cmp::min(*balance, spec.max_deposit_amount))
.map(|v| v.effective_balance)
.ok_or_else(|| Error::UnknownValidator)
}
/// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
@ -631,37 +722,38 @@ impl<T: EthSpec> BeaconState<T> {
epoch + 1 + spec.activation_exit_delay
}
/// Initiate an exit for the validator of the given `index`.
/// Return the churn limit for the current epoch (number of validators who can leave per epoch).
///
/// Spec v0.5.1
pub fn initiate_validator_exit(&mut self, validator_index: usize) {
self.validator_registry[validator_index].initiated_exit = true;
/// Uses the epoch cache, and will error if it isn't initialized.
///
/// Spec v0.6.1
pub fn get_churn_limit(&self, spec: &ChainSpec) -> Result<u64, Error> {
Ok(std::cmp::max(
spec.min_per_epoch_churn_limit,
self.cache(RelativeEpoch::Current)?.active_validator_count() as u64
/ spec.churn_limit_quotient,
))
}
/// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an
/// attestation.
///
/// Only reads the current epoch.
///
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
///
/// Spec v0.5.1
/// Spec v0.6.2
pub fn get_attestation_duties(
&self,
validator_index: usize,
spec: &ChainSpec,
) -> Result<&Option<AttestationDuty>, Error> {
let cache = self.cache(RelativeEpoch::Current, spec)?;
relative_epoch: RelativeEpoch,
) -> Result<Option<AttestationDuty>, Error> {
let cache = self.cache(relative_epoch)?;
Ok(cache
.attestation_duties
.get(validator_index)
.ok_or_else(|| Error::UnknownValidator)?)
Ok(cache.get_attestation_duties(validator_index))
}
/// Return the combined effective balance of an array of validators.
///
/// Spec v0.5.1
/// Spec v0.6.0
pub fn get_total_balance(
&self,
validator_indices: &[usize],
@ -675,42 +767,52 @@ impl<T: EthSpec> BeaconState<T> {
/// Build all the caches, if they need to be built.
pub fn build_all_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> {
self.build_epoch_cache(RelativeEpoch::Previous, spec)?;
self.build_epoch_cache(RelativeEpoch::Current, spec)?;
self.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec)?;
self.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?;
self.build_committee_cache(RelativeEpoch::Previous, spec)?;
self.build_committee_cache(RelativeEpoch::Current, spec)?;
self.build_committee_cache(RelativeEpoch::Next, spec)?;
self.update_pubkey_cache()?;
self.update_tree_hash_cache()?;
self.exit_cache
.build_from_registry(&self.validator_registry, spec);
Ok(())
}
/// Drop all caches on the state.
pub fn drop_all_caches(&mut self) {
self.drop_committee_cache(RelativeEpoch::Previous);
self.drop_committee_cache(RelativeEpoch::Current);
self.drop_committee_cache(RelativeEpoch::Next);
self.drop_pubkey_cache();
self.drop_tree_hash_cache();
self.exit_cache = ExitCache::default();
}
/// Build an epoch cache, unless it is has already been built.
pub fn build_epoch_cache(
pub fn build_committee_cache(
&mut self,
relative_epoch: RelativeEpoch,
spec: &ChainSpec,
) -> Result<(), Error> {
let cache_index = self.cache_index(relative_epoch);
let i = Self::cache_index(relative_epoch);
if self.caches[cache_index].initialized_epoch == Some(self.slot.epoch(spec.slots_per_epoch))
{
if self.committee_caches[i].is_initialized_at(self.previous_epoch()) {
Ok(())
} else {
self.force_build_epoch_cache(relative_epoch, spec)
self.force_build_committee_cache(relative_epoch, spec)
}
}
/// Always builds an epoch cache, even if it is already initialized.
pub fn force_build_epoch_cache(
/// Always builds the previous epoch cache, even if it is already initialized.
pub fn force_build_committee_cache(
&mut self,
relative_epoch: RelativeEpoch,
spec: &ChainSpec,
) -> Result<(), Error> {
let cache_index = self.cache_index(relative_epoch);
self.caches[cache_index] = EpochCache::initialized(&self, relative_epoch, spec)?;
let epoch = relative_epoch.into_epoch(self.current_epoch());
self.committee_caches[Self::cache_index(relative_epoch)] =
CommitteeCache::initialized(&self, epoch, spec)?;
Ok(())
}
@ -718,51 +820,42 @@ impl<T: EthSpec> BeaconState<T> {
///
/// This should be used if the `slot` of this state is advanced beyond an epoch boundary.
///
/// The `Next` cache becomes the `Current` and the `Current` cache becomes the `Previous`. The
/// `Previous` cache is abandoned.
///
/// Care should be taken to update the `Current` epoch in case a registry update is performed
/// -- `Next` epoch is always _without_ a registry change. If you perform a registry update,
/// you should rebuild the `Current` cache so it uses the new seed.
/// Note: whilst this function will preserve already-built caches, it will not build any.
pub fn advance_caches(&mut self) {
self.drop_cache(RelativeEpoch::Previous);
let next = Self::cache_index(RelativeEpoch::Previous);
self.cache_index_offset += 1;
self.cache_index_offset %= CACHED_EPOCHS;
let caches = &mut self.committee_caches[..];
caches.rotate_left(1);
caches[next] = CommitteeCache::default();
}
/// Removes the specified cache and sets it to uninitialized.
pub fn drop_cache(&mut self, relative_epoch: RelativeEpoch) {
let previous_cache_index = self.cache_index(relative_epoch);
self.caches[previous_cache_index] = EpochCache::default();
}
/// Returns the index of `self.caches` for some `RelativeEpoch`.
fn cache_index(&self, relative_epoch: RelativeEpoch) -> usize {
let base_index = match relative_epoch {
fn cache_index(relative_epoch: RelativeEpoch) -> usize {
match relative_epoch {
RelativeEpoch::Previous => 0,
RelativeEpoch::Current => 1,
RelativeEpoch::NextWithoutRegistryChange => 2,
RelativeEpoch::NextWithRegistryChange => 3,
};
(base_index + self.cache_index_offset) % CACHED_EPOCHS
RelativeEpoch::Next => 2,
}
}
/// Returns the cache for some `RelativeEpoch`. Returns an error if the cache has not been
/// initialized.
fn cache(&self, relative_epoch: RelativeEpoch, spec: &ChainSpec) -> Result<&EpochCache, Error> {
let cache = &self.caches[self.cache_index(relative_epoch)];
fn cache(&self, relative_epoch: RelativeEpoch) -> Result<&CommitteeCache, Error> {
let cache = &self.committee_caches[Self::cache_index(relative_epoch)];
let epoch = relative_epoch.into_epoch(self.slot.epoch(spec.slots_per_epoch));
if cache.initialized_epoch == Some(epoch) {
if cache.is_initialized_at(relative_epoch.into_epoch(self.current_epoch())) {
Ok(cache)
} else {
Err(Error::EpochCacheUninitialized(relative_epoch))
Err(Error::CommitteeCacheUninitialized(relative_epoch))
}
}
/// Drops the cache, leaving it in an uninitialized state.
fn drop_committee_cache(&mut self, relative_epoch: RelativeEpoch) {
self.committee_caches[Self::cache_index(relative_epoch)] = CommitteeCache::default();
}
// FIXME(sproul): drop_previous/current_committee_cache
/// Updates the pubkey cache, if required.
///
/// Adds all `pubkeys` from the `validator_registry` which are not already in the cache. Will
@ -820,6 +913,11 @@ impl<T: EthSpec> BeaconState<T> {
.and_then(|b| Ok(Hash256::from_slice(b)))
.map_err(Into::into)
}
/// Completely drops the tree hash cache, replacing it with a new, empty cache.
pub fn drop_tree_hash_cache(&mut self) {
self.tree_hash_cache = TreeHashCache::default()
}
}
impl From<RelativeEpochError> for Error {
@ -828,12 +926,6 @@ impl From<RelativeEpochError> for Error {
}
}
impl From<EpochCacheError> for Error {
fn from(e: EpochCacheError) -> Error {
Error::EpochCacheError(e)
}
}
impl From<TreeHashCacheError> for Error {
fn from(e: TreeHashCacheError) -> Error {
Error::TreeHashCacheError(e)

View File

@ -3,9 +3,7 @@ use fixed_len_vec::typenum::{Unsigned, U1024, U8, U8192};
use serde_derive::{Deserialize, Serialize};
use std::fmt::Debug;
pub trait EthSpec:
'static + Default + Sync + Send + Clone + Debug + PartialEq + serde::de::DeserializeOwned
{
pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq {
type ShardCount: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type SlotsPerHistoricalRoot: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type LatestRandaoMixesLength: Unsigned + Clone + Sync + Send + Debug + PartialEq;
@ -14,37 +12,77 @@ pub trait EthSpec:
fn spec() -> ChainSpec;
/// Return the number of committees in one epoch.
///
/// Spec v0.6.1
fn get_epoch_committee_count(active_validator_count: usize) -> usize {
let target_committee_size = Self::spec().target_committee_size;
let shard_count = Self::shard_count();
let slots_per_epoch = Self::slots_per_epoch() as usize;
std::cmp::max(
1,
std::cmp::min(
shard_count / slots_per_epoch,
active_validator_count / slots_per_epoch / target_committee_size,
),
) * slots_per_epoch
}
/// Returns the minimum number of validators required for this spec.
///
/// This is the _absolute_ minimum, the number required to make the chain operate in the most
/// basic sense. This count is not required to provide any security guarantees regarding
/// decentralization, entropy, etc.
fn minimum_validator_count() -> usize {
Self::slots_per_epoch() as usize
}
/// Returns the `SLOTS_PER_EPOCH` constant for this specification.
///
/// Spec v0.6.1
fn slots_per_epoch() -> u64 {
Self::spec().slots_per_epoch
}
/// Returns the `SLOTS_PER_EPOCH` constant for this specification.
///
/// Spec v0.6.1
fn genesis_epoch() -> Epoch {
Self::spec().genesis_epoch
}
/// Returns the `SHARD_COUNT` constant for this specification.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn shard_count() -> usize {
Self::ShardCount::to_usize()
}
/// Returns the `SLOTS_PER_HISTORICAL_ROOT` constant for this specification.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn slots_per_historical_root() -> usize {
Self::SlotsPerHistoricalRoot::to_usize()
}
/// Returns the `LATEST_RANDAO_MIXES_LENGTH` constant for this specification.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn latest_randao_mixes_length() -> usize {
Self::LatestRandaoMixesLength::to_usize()
}
/// Returns the `LATEST_ACTIVE_INDEX_ROOTS` constant for this specification.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn latest_active_index_roots() -> usize {
Self::LatestActiveIndexRootsLength::to_usize()
}
/// Returns the `LATEST_SLASHED_EXIT_LENGTH` constant for this specification.
///
/// Spec v0.5.1
/// Spec v0.6.1
fn latest_slashed_exit_length() -> usize {
Self::LatestSlashedExitLength::to_usize()
}
@ -52,7 +90,7 @@ pub trait EthSpec:
/// Ethereum Foundation specifications.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct FoundationEthSpec;
@ -71,8 +109,6 @@ impl EthSpec for FoundationEthSpec {
pub type FoundationBeaconState = BeaconState<FoundationEthSpec>;
/// Ethereum Foundation specifications, modified to be suitable for < 1000 validators.
///
/// Spec v0.5.1
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct FewValidatorsEthSpec;
@ -91,8 +127,6 @@ impl EthSpec for FewValidatorsEthSpec {
pub type FewValidatorsBeaconState = BeaconState<FewValidatorsEthSpec>;
/// Specifications suitable for a small-scale (< 1000 validators) lighthouse testnet.
///
/// Spec v0.5.1
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct LighthouseTestnetEthSpec;

View File

@ -0,0 +1,323 @@
use super::BeaconState;
use crate::*;
use core::num::NonZeroUsize;
use serde_derive::{Deserialize, Serialize};
use std::ops::Range;
use swap_or_not_shuffle::shuffle_list;
mod tests;
/// Computes and stores the shuffling for an epoch. Provides various getters to allow callers to
/// read the committees for the given epoch.
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct CommitteeCache {
initialized_epoch: Option<Epoch>,
shuffling: Vec<usize>,
shuffling_positions: Vec<Option<NonZeroUsize>>,
shuffling_start_shard: u64,
shard_count: u64,
committee_count: usize,
slots_per_epoch: u64,
}
impl CommitteeCache {
/// Return a new, fully initialized cache.
///
/// Spec v0.6.1
pub fn initialized<T: EthSpec>(
state: &BeaconState<T>,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<CommitteeCache, Error> {
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), epoch)
.map_err(|_| Error::EpochOutOfBounds)?;
// May cause divide-by-zero errors.
if T::slots_per_epoch() == 0 {
return Err(Error::ZeroSlotsPerEpoch);
}
let active_validator_indices =
get_active_validator_indices(&state.validator_registry, epoch);
if active_validator_indices.is_empty() {
return Err(Error::InsufficientValidators);
}
let committee_count = T::get_epoch_committee_count(active_validator_indices.len()) as usize;
let shuffling_start_shard = match relative_epoch {
RelativeEpoch::Current => state.latest_start_shard,
RelativeEpoch::Previous => {
let committees_in_previous_epoch =
T::get_epoch_committee_count(active_validator_indices.len()) as u64;
(state.latest_start_shard + T::shard_count() as u64 - committees_in_previous_epoch)
% T::shard_count() as u64
}
RelativeEpoch::Next => {
let current_active_validators =
get_active_validator_count(&state.validator_registry, state.current_epoch());
let committees_in_current_epoch =
T::get_epoch_committee_count(current_active_validators) as u64;
(state.latest_start_shard + committees_in_current_epoch) % T::shard_count() as u64
}
};
let seed = state.generate_seed(epoch, spec)?;
let shuffling = shuffle_list(
active_validator_indices,
spec.shuffle_round_count,
&seed[..],
false,
)
.ok_or_else(|| Error::UnableToShuffle)?;
// The use of `NonZeroUsize` reduces the maximum number of possible validators by one.
if state.validator_registry.len() > usize::max_value() - 1 {
return Err(Error::TooManyValidators);
}
let mut shuffling_positions = vec![None; state.validator_registry.len()];
for (i, v) in shuffling.iter().enumerate() {
shuffling_positions[*v] = NonZeroUsize::new(i + 1);
}
Ok(CommitteeCache {
initialized_epoch: Some(epoch),
shuffling_start_shard,
shuffling,
shard_count: T::shard_count() as u64,
committee_count,
slots_per_epoch: T::slots_per_epoch(),
shuffling_positions,
})
}
/// Returns `true` if the cache has been initialized at the supplied `epoch`.
///
/// An non-initialized cache does not provide any useful information.
pub fn is_initialized_at(&self, epoch: Epoch) -> bool {
Some(epoch) == self.initialized_epoch
}
/// Returns the **shuffled** list of active validator indices for the initialized epoch.
///
/// These indices are not in ascending order.
///
/// Always returns `&[]` for a non-initialized epoch.
///
/// Spec v0.6.1
pub fn active_validator_indices(&self) -> &[usize] {
&self.shuffling
}
/// Returns the shuffled list of active validator indices for the initialized epoch.
///
/// Always returns `&[]` for a non-initialized epoch.
///
/// Spec v0.6.1
pub fn shuffling(&self) -> &[usize] {
&self.shuffling
}
/// Return `Some(CrosslinkCommittee)` if the given shard has a committee during the given
/// `epoch`.
///
/// Always returns `None` for a non-initialized epoch.
///
/// Spec v0.6.1
pub fn get_crosslink_committee_for_shard(&self, shard: Shard) -> Option<CrosslinkCommittee> {
if shard >= self.shard_count || self.initialized_epoch.is_none() {
return None;
}
let committee_index =
(shard + self.shard_count - self.shuffling_start_shard) % self.shard_count;
let committee = self.compute_committee(committee_index as usize)?;
let slot = self.crosslink_slot_for_shard(shard)?;
Some(CrosslinkCommittee {
shard,
committee,
slot,
})
}
/// Returns the `AttestationDuty` for the given `validator_index`.
///
/// Returns `None` if the `validator_index` does not exist, does not have duties or `Self` is
/// non-initialized.
pub fn get_attestation_duties(&self, validator_index: usize) -> Option<AttestationDuty> {
let i = self.shuffled_position(validator_index)?;
(0..self.committee_count)
.into_iter()
.map(|nth_committee| (nth_committee, self.compute_committee_range(nth_committee)))
.find(|(_, range)| {
if let Some(range) = range {
(range.start <= i) && (range.end > i)
} else {
false
}
})
.and_then(|(nth_committee, range)| {
let shard = (self.shuffling_start_shard + nth_committee as u64) % self.shard_count;
let slot = self.crosslink_slot_for_shard(shard)?;
let range = range?;
let committee_index = i - range.start;
let committee_len = range.end - range.start;
Some(AttestationDuty {
slot,
shard,
committee_index,
committee_len,
})
})
}
/// Returns the number of active validators in the initialized epoch.
///
/// Always returns `usize::default()` for a non-initialized epoch.
///
/// Spec v0.6.1
pub fn active_validator_count(&self) -> usize {
self.shuffling.len()
}
/// Returns the total number of committees in the initialized epoch.
///
/// Always returns `usize::default()` for a non-initialized epoch.
///
/// Spec v0.6.1
pub fn epoch_committee_count(&self) -> usize {
self.committee_count
}
/// Returns the shard assigned to the first committee in the initialized epoch.
///
/// Always returns `u64::default()` for a non-initialized epoch.
pub fn epoch_start_shard(&self) -> u64 {
self.shuffling_start_shard
}
/// Returns all crosslink committees, if any, for the given slot in the initialized epoch.
///
/// Returns `None` if `slot` is not in the initialized epoch, or if `Self` is not initialized.
///
/// Spec v0.6.1
pub fn get_crosslink_committees_for_slot(&self, slot: Slot) -> Option<Vec<CrosslinkCommittee>> {
let position = self
.initialized_epoch?
.position(slot, self.slots_per_epoch)?;
let committees_per_slot = self.committee_count / self.slots_per_epoch as usize;
let position = position * committees_per_slot;
if position >= self.committee_count {
None
} else {
let mut committees = Vec::with_capacity(committees_per_slot);
for index in position..position + committees_per_slot {
let committee = self.compute_committee(index)?;
let shard = (self.shuffling_start_shard + index as u64) % self.shard_count;
committees.push(CrosslinkCommittee {
committee,
shard,
slot,
});
}
Some(committees)
}
}
/// Returns the first committee of the first slot of the initialized epoch.
///
/// Always returns `None` for a non-initialized epoch.
///
/// Spec v0.6.1
pub fn first_committee_at_slot(&self, slot: Slot) -> Option<&[usize]> {
self.get_crosslink_committees_for_slot(slot)?
.first()
.and_then(|cc| Some(cc.committee))
}
/// Returns a slice of `self.shuffling` that represents the `index`'th committee in the epoch.
///
/// Spec v0.6.1
fn compute_committee(&self, index: usize) -> Option<&[usize]> {
Some(&self.shuffling[self.compute_committee_range(index)?])
}
/// Returns a range of `self.shuffling` that represents the `index`'th committee in the epoch.
///
/// To avoid a divide-by-zero, returns `None` if `self.committee_count` is zero.
///
/// Spec v0.6.1
fn compute_committee_range(&self, index: usize) -> Option<Range<usize>> {
if self.committee_count == 0 {
return None;
}
let num_validators = self.shuffling.len();
let count = self.committee_count;
let start = (num_validators * index) / count;
let end = (num_validators * (index + 1)) / count;
Some(start..end)
}
/// Returns the `slot` that `shard` will be crosslink-ed in during the initialized epoch.
///
/// Always returns `None` for a non-initialized epoch.
///
/// Spec v0.6.1
fn crosslink_slot_for_shard(&self, shard: u64) -> Option<Slot> {
let offset = (shard + self.shard_count - self.shuffling_start_shard) % self.shard_count;
Some(
self.initialized_epoch?.start_slot(self.slots_per_epoch)
+ offset / (self.committee_count as u64 / self.slots_per_epoch),
)
}
/// Returns the index of some validator in `self.shuffling`.
///
/// Always returns `None` for a non-initialized epoch.
fn shuffled_position(&self, validator_index: usize) -> Option<usize> {
self.shuffling_positions
.get(validator_index)?
.and_then(|p| Some(p.get() - 1))
}
}
/// Returns a list of all `validator_registry` indices where the validator is active at the given
/// `epoch`.
///
/// Spec v0.6.1
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
}
/// Returns the count of all `validator_registry` indices where the validator is active at the given
/// `epoch`.
///
/// Spec v0.6.1
fn get_active_validator_count(validators: &[Validator], epoch: Epoch) -> usize {
validators.iter().filter(|v| v.is_active_at(epoch)).count()
}

View File

@ -0,0 +1,241 @@
#![cfg(test)]
use super::*;
use crate::{test_utils::*, *};
use fixed_len_vec::typenum::*;
use serde_derive::{Deserialize, Serialize};
#[test]
fn default_values() {
let cache = CommitteeCache::default();
assert_eq!(cache.is_initialized_at(Epoch::new(0)), false);
assert_eq!(cache.active_validator_indices(), &[]);
assert_eq!(cache.get_crosslink_committee_for_shard(0), None);
assert_eq!(cache.get_attestation_duties(0), None);
assert_eq!(cache.active_validator_count(), 0);
assert_eq!(cache.epoch_committee_count(), 0);
assert_eq!(cache.epoch_start_shard(), 0);
assert_eq!(cache.get_crosslink_committees_for_slot(Slot::new(0)), None);
assert_eq!(cache.first_committee_at_slot(Slot::new(0)), None);
}
fn new_state<T: EthSpec>(validator_count: usize, slot: Slot) -> BeaconState<T> {
let spec = &T::spec();
let mut builder =
TestingBeaconStateBuilder::from_single_keypair(validator_count, &Keypair::random(), spec);
builder.teleport_to_slot(slot, spec);
let (state, _keypairs) = builder.build();
state
}
#[test]
fn fails_without_validators() {
let state = new_state::<FewValidatorsEthSpec>(0, Slot::new(0));
let spec = &FewValidatorsEthSpec::spec();
assert_eq!(
CommitteeCache::initialized(&state, state.current_epoch(), &spec),
Err(BeaconStateError::InsufficientValidators)
);
}
#[test]
fn initializes_with_the_right_epoch() {
let state = new_state::<FewValidatorsEthSpec>(16, Slot::new(0));
let spec = &FewValidatorsEthSpec::spec();
let cache = CommitteeCache::default();
assert_eq!(cache.initialized_epoch, None);
let cache = CommitteeCache::initialized(&state, state.current_epoch(), &spec).unwrap();
assert_eq!(cache.initialized_epoch, Some(state.current_epoch()));
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), &spec).unwrap();
assert_eq!(cache.initialized_epoch, Some(state.previous_epoch()));
let cache = CommitteeCache::initialized(&state, state.next_epoch(), &spec).unwrap();
assert_eq!(cache.initialized_epoch, Some(state.next_epoch()));
}
#[test]
fn shuffles_for_the_right_epoch() {
let num_validators = FewValidatorsEthSpec::minimum_validator_count() * 2;
let epoch = Epoch::new(100_000_000);
let slot = epoch.start_slot(FewValidatorsEthSpec::slots_per_epoch());
let mut state = new_state::<FewValidatorsEthSpec>(num_validators, slot);
let spec = &FewValidatorsEthSpec::spec();
let distinct_hashes: Vec<Hash256> = (0..FewValidatorsEthSpec::latest_randao_mixes_length())
.into_iter()
.map(|i| Hash256::from(i as u64))
.collect();
state.latest_randao_mixes = FixedLenVec::from(distinct_hashes);
let previous_seed = state.generate_seed(state.previous_epoch(), spec).unwrap();
let current_seed = state.generate_seed(state.current_epoch(), spec).unwrap();
let next_seed = state.generate_seed(state.next_epoch(), spec).unwrap();
assert!((previous_seed != current_seed) && (current_seed != next_seed));
let shuffling_with_seed = |seed: Hash256| {
shuffle_list(
(0..num_validators).collect(),
spec.shuffle_round_count,
&seed[..],
false,
)
.unwrap()
};
let assert_shuffling_positions_accurate = |cache: &CommitteeCache| {
for (i, v) in cache.shuffling.iter().enumerate() {
assert_eq!(
cache.shuffling_positions[*v].unwrap().get() - 1,
i,
"Shuffling position inaccurate"
);
}
};
let cache = CommitteeCache::initialized(&state, state.current_epoch(), spec).unwrap();
assert_eq!(cache.shuffling, shuffling_with_seed(current_seed));
assert_shuffling_positions_accurate(&cache);
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), spec).unwrap();
assert_eq!(cache.shuffling, shuffling_with_seed(previous_seed));
assert_shuffling_positions_accurate(&cache);
let cache = CommitteeCache::initialized(&state, state.next_epoch(), spec).unwrap();
assert_eq!(cache.shuffling, shuffling_with_seed(next_seed));
assert_shuffling_positions_accurate(&cache);
}
#[test]
fn can_start_on_any_shard() {
let num_validators = FewValidatorsEthSpec::minimum_validator_count() * 2;
let epoch = Epoch::new(100_000_000);
let slot = epoch.start_slot(FewValidatorsEthSpec::slots_per_epoch());
let mut state = new_state::<FewValidatorsEthSpec>(num_validators, slot);
let spec = &FewValidatorsEthSpec::spec();
for i in 0..FewValidatorsEthSpec::shard_count() as u64 {
state.latest_start_shard = i;
let cache = CommitteeCache::initialized(&state, state.current_epoch(), spec).unwrap();
assert_eq!(cache.shuffling_start_shard, i);
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), spec).unwrap();
assert_eq!(cache.shuffling_start_shard, i);
let cache = CommitteeCache::initialized(&state, state.next_epoch(), spec).unwrap();
assert_eq!(cache.shuffling_start_shard, i);
}
}
/// This spec has more shards than slots in an epoch, permitting epochs where not all shards are
/// included in the committee.
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct ExcessShardsEthSpec;
impl EthSpec for ExcessShardsEthSpec {
type ShardCount = U128;
type SlotsPerHistoricalRoot = U8192;
type LatestRandaoMixesLength = U8192;
type LatestActiveIndexRootsLength = U8192;
type LatestSlashedExitLength = U8192;
fn spec() -> ChainSpec {
ChainSpec::few_validators()
}
}
#[test]
fn starts_on_the_correct_shard() {
let spec = &ExcessShardsEthSpec::spec();
let num_validators = ExcessShardsEthSpec::shard_count();
let epoch = Epoch::new(100_000_000);
let slot = epoch.start_slot(ExcessShardsEthSpec::slots_per_epoch());
let mut state = new_state::<ExcessShardsEthSpec>(num_validators, slot);
let validator_count = state.validator_registry.len();
let previous_epoch = state.previous_epoch();
let current_epoch = state.current_epoch();
let next_epoch = state.next_epoch();
for (i, mut v) in state.validator_registry.iter_mut().enumerate() {
let epoch = if i < validator_count / 4 {
previous_epoch
} else if i < validator_count / 2 {
current_epoch
} else {
next_epoch
};
v.activation_epoch = epoch;
}
assert_eq!(
get_active_validator_count(&state.validator_registry, previous_epoch),
validator_count / 4
);
assert_eq!(
get_active_validator_count(&state.validator_registry, current_epoch),
validator_count / 2
);
assert_eq!(
get_active_validator_count(&state.validator_registry, next_epoch),
validator_count
);
let previous_shards = ExcessShardsEthSpec::get_epoch_committee_count(
get_active_validator_count(&state.validator_registry, previous_epoch),
);
let current_shards = ExcessShardsEthSpec::get_epoch_committee_count(
get_active_validator_count(&state.validator_registry, current_epoch),
);
let next_shards = ExcessShardsEthSpec::get_epoch_committee_count(get_active_validator_count(
&state.validator_registry,
next_epoch,
));
assert_eq!(
previous_shards as usize,
ExcessShardsEthSpec::shard_count() / 4
);
assert_eq!(
current_shards as usize,
ExcessShardsEthSpec::shard_count() / 2
);
assert_eq!(next_shards as usize, ExcessShardsEthSpec::shard_count());
let shard_count = ExcessShardsEthSpec::shard_count();
for i in 0..ExcessShardsEthSpec::shard_count() {
state.latest_start_shard = i as u64;
let cache = CommitteeCache::initialized(&state, state.current_epoch(), spec).unwrap();
assert_eq!(cache.shuffling_start_shard as usize, i);
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), spec).unwrap();
assert_eq!(
cache.shuffling_start_shard as usize,
(i + shard_count - previous_shards) % shard_count
);
let cache = CommitteeCache::initialized(&state, state.next_epoch(), spec).unwrap();
assert_eq!(
cache.shuffling_start_shard as usize,
(i + current_shards) % shard_count
);
}
}

View File

@ -1,325 +0,0 @@
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)]
pub enum Error {
UnableToShuffle,
UnableToGenerateSeed,
}
mod tests;
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct EpochCache {
/// `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_duties: Vec<Option<AttestationDuty>>,
/// Maps a shard to an index of `self.committees`.
pub shard_committee_indices: Vec<Option<(Slot, usize)>>,
/// Indices of all active validators in the epoch
pub active_validator_indices: Vec<usize>,
}
impl EpochCache {
/// Return a new, fully initialized cache.
pub fn initialized<T: EthSpec>(
state: &BeaconState<T>,
relative_epoch: RelativeEpoch,
spec: &ChainSpec,
) -> Result<EpochCache, Error> {
let epoch = relative_epoch.into_epoch(state.slot.epoch(spec.slots_per_epoch));
let active_validator_indices =
get_active_validator_indices(&state.validator_registry, epoch);
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)?;
// 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;
for (j, crosslink_committee) in slot_committees.iter().enumerate() {
let shard = crosslink_committee.shard;
shard_committee_indices[shard as usize] = Some((slot, j));
for (k, validator_index) in crosslink_committee.committee.iter().enumerate() {
let attestation_duty = AttestationDuty {
slot,
shard,
committee_index: k,
committee_len: crosslink_committee.committee.len(),
};
attestation_duties[*validator_index] = Some(attestation_duty)
}
}
}
Ok(EpochCache {
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.1
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<T: EthSpec>(
state: &BeaconState<T>,
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<T: EthSpec>(
state: &BeaconState<T>,
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<T: EthSpec>(
state: &BeaconState<T>,
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[..],
false,
)
.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)
}
}

View File

@ -1,159 +0,0 @@
#![cfg(test)]
use super::*;
use crate::beacon_state::FewValidatorsEthSpec;
use crate::test_utils::*;
use swap_or_not_shuffle::shuffle_list;
fn do_sane_cache_test<T: EthSpec>(
state: BeaconState<T>,
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[..],
false,
)
.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<T: EthSpec>(validator_count: usize, spec: &ChainSpec) -> BeaconState<T> {
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 = FewValidatorsEthSpec::spec();
spec.shard_count = 4;
let validator_count = (spec.shard_count * spec.target_committee_size) + 1;
let state: BeaconState<FewValidatorsEthSpec> =
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 = FewValidatorsEthSpec::spec();
spec.shard_count = 2;
let validator_count = (spec.shard_count * spec.target_committee_size) + 1;
let state: BeaconState<FewValidatorsEthSpec> =
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 = FewValidatorsEthSpec::spec();
spec.shard_count = 2;
let validator_count = (spec.shard_count * spec.target_committee_size) + 1;
let mut state: BeaconState<FewValidatorsEthSpec> =
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,
);
}

View File

@ -0,0 +1,35 @@
use super::{ChainSpec, Epoch, Validator};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
/// Map from exit epoch to the number of validators known to be exiting/exited at that epoch.
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExitCache(HashMap<Epoch, u64>);
impl ExitCache {
/// Add all validators with a non-trivial exit epoch to the cache.
pub fn build_from_registry(&mut self, validator_registry: &[Validator], spec: &ChainSpec) {
validator_registry
.iter()
.filter(|validator| validator.exit_epoch != spec.far_future_epoch)
.for_each(|validator| self.record_validator_exit(validator.exit_epoch));
}
/// Record the exit of a single validator in the cache.
///
/// Must only be called once per exiting validator.
pub fn record_validator_exit(&mut self, exit_epoch: Epoch) {
*self.0.entry(exit_epoch).or_insert(0) += 1;
}
/// Get the greatest epoch for which validator exits are known.
pub fn max_epoch(&self) -> Option<Epoch> {
// This could probably be made even faster by caching the maximum.
self.0.keys().max().cloned()
}
/// Get the number of validators exiting/exited at a given epoch, or zero if not known.
pub fn get_churn_at(&self, epoch: Epoch) -> u64 {
self.0.get(&epoch).cloned().unwrap_or(0)
}
}

View File

@ -1,11 +1,127 @@
#![cfg(test)]
use super::*;
use crate::beacon_state::FewValidatorsEthSpec;
use crate::test_utils::*;
use std::ops::RangeInclusive;
ssz_tests!(FoundationBeaconState);
cached_tree_hash_tests!(FoundationBeaconState);
fn test_beacon_proposer_index<T: EthSpec>() {
let spec = T::spec();
let relative_epoch = RelativeEpoch::Current;
// Build a state for testing.
let build_state = |validator_count: usize| -> BeaconState<T> {
let builder: TestingBeaconStateBuilder<T> =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
let (mut state, _keypairs) = builder.build();
state.build_committee_cache(relative_epoch, &spec).unwrap();
state
};
// Run a test on the state.
let test = |state: &BeaconState<T>, slot: Slot, shuffling_index: usize| {
let shuffling = state.get_shuffling(relative_epoch).unwrap();
assert_eq!(
state.get_beacon_proposer_index(slot, relative_epoch, &spec),
Ok(shuffling[shuffling_index])
);
};
// Test where we have one validator per slot
let state = build_state(T::slots_per_epoch() as usize);
for i in 0..T::slots_per_epoch() {
test(&state, Slot::from(i), i as usize);
}
// Test where we have two validators per slot
let state = build_state(T::slots_per_epoch() as usize * 2);
for i in 0..T::slots_per_epoch() {
test(&state, Slot::from(i), i as usize * 2);
}
// Test with two validators per slot, first validator has zero balance.
let mut state = build_state(T::slots_per_epoch() as usize * 2);
let shuffling = state.get_shuffling(relative_epoch).unwrap().to_vec();
state.validator_registry[shuffling[0]].effective_balance = 0;
test(&state, Slot::new(0), 1);
for i in 1..T::slots_per_epoch() {
test(&state, Slot::from(i), i as usize * 2);
}
}
#[test]
fn beacon_proposer_index() {
test_beacon_proposer_index::<FewValidatorsEthSpec>();
}
/// Should produce (note the set notation brackets):
///
/// (current_epoch - LATEST_ACTIVE_INDEX_ROOTS_LENGTH + ACTIVATION_EXIT_DELAY, current_epoch +
/// ACTIVATION_EXIT_DELAY]
fn active_index_range<T: EthSpec>(current_epoch: Epoch) -> RangeInclusive<Epoch> {
let delay = T::spec().activation_exit_delay;
let start: i32 =
current_epoch.as_u64() as i32 - T::latest_active_index_roots() as i32 + delay as i32;
let end = current_epoch + delay;
let start: Epoch = if start < 0 {
Epoch::new(0)
} else {
Epoch::from(start as u64 + 1)
};
start..=end
}
/// Test getting an active index root at the start and end of the valid range, and one either side
/// of that range.
fn test_active_index<T: EthSpec>(state_slot: Slot) {
let spec = T::spec();
let builder: TestingBeaconStateBuilder<T> =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
let (mut state, _keypairs) = builder.build();
state.slot = state_slot;
let range = active_index_range::<T>(state.current_epoch());
let modulo = |epoch: Epoch| epoch.as_usize() % T::latest_active_index_roots();
// Test the start and end of the range.
assert_eq!(
state.get_active_index_root_index(*range.start(), &spec),
Ok(modulo(*range.start()))
);
assert_eq!(
state.get_active_index_root_index(*range.end(), &spec),
Ok(modulo(*range.end()))
);
// One either side of the range.
if state.current_epoch() > 0 {
// Test is invalid on epoch zero, cannot subtract from zero.
assert_eq!(
state.get_active_index_root_index(*range.start() - 1, &spec),
Err(Error::EpochOutOfBounds)
);
}
assert_eq!(
state.get_active_index_root_index(*range.end() + 1, &spec),
Err(Error::EpochOutOfBounds)
);
}
#[test]
fn get_active_index_root_index() {
test_active_index::<FoundationEthSpec>(Slot::new(0));
let epoch = Epoch::from(FoundationEthSpec::latest_active_index_roots() * 4);
let slot = epoch.start_slot(FoundationEthSpec::slots_per_epoch());
test_active_index::<FoundationEthSpec>(slot);
}
/// Test that
///
/// 1. Using the cache before it's built fails.
@ -22,12 +138,14 @@ fn test_cache_initialization<'a, T: EthSpec>(
// 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))
state.get_attestation_duties(0, relative_epoch),
Err(BeaconStateError::CommitteeCacheUninitialized(
relative_epoch
))
);
// Build the cache.
state.build_epoch_cache(relative_epoch, spec).unwrap();
state.build_committee_cache(relative_epoch, spec).unwrap();
// Assert a call to a cache-using function passes.
let _ = state
@ -35,12 +153,14 @@ fn test_cache_initialization<'a, T: EthSpec>(
.unwrap();
// Drop the cache.
state.drop_cache(relative_epoch);
state.drop_committee_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))
Err(BeaconStateError::CommitteeCacheUninitialized(
relative_epoch
))
);
}
@ -56,8 +176,7 @@ fn cache_initialization() {
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);
test_cache_initialization(&mut state, RelativeEpoch::Next, &spec);
}
#[test]
@ -78,3 +197,165 @@ fn tree_hash_cache() {
let root = state.update_tree_hash_cache().unwrap();
assert_eq!(root.as_bytes(), &state.tree_hash_root()[..]);
}
/// Tests committee-specific components
#[cfg(test)]
mod committees {
use super::*;
use crate::beacon_state::FewValidatorsEthSpec;
use swap_or_not_shuffle::shuffle_list;
fn execute_committee_consistency_test<T: EthSpec>(
state: BeaconState<T>,
epoch: Epoch,
validator_count: usize,
spec: &ChainSpec,
) {
let active_indices: Vec<usize> = (0..validator_count).collect();
let seed = state.generate_seed(epoch, spec).unwrap();
let start_shard = 0;
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), epoch).unwrap();
let mut ordered_indices = state
.get_cached_active_validator_indices(relative_epoch)
.unwrap()
.to_vec();
ordered_indices.sort_unstable();
assert_eq!(
active_indices, ordered_indices,
"Validator indices mismatch"
);
let shuffling =
shuffle_list(active_indices, spec.shuffle_round_count, &seed[..], false).unwrap();
let mut expected_indices_iter = shuffling.iter();
let mut expected_shards_iter =
(start_shard..start_shard + T::shard_count() as u64).into_iter();
// Loop through all slots in the epoch being tested.
for slot in epoch.slot_iter(spec.slots_per_epoch) {
let crosslink_committees = state.get_crosslink_committees_at_slot(slot).unwrap();
// Assert that the number of committees in this slot is consistent with the reported number
// of committees in an epoch.
assert_eq!(
crosslink_committees.len() as u64,
state.get_epoch_committee_count(relative_epoch).unwrap() / T::slots_per_epoch()
);
for cc in crosslink_committees {
// Assert that shards are assigned contiguously across committees.
assert_eq!(expected_shards_iter.next().unwrap(), cc.shard);
// Assert that a committee lookup via slot is identical to a committee lookup via
// shard.
assert_eq!(
state
.get_crosslink_committee_for_shard(cc.shard, relative_epoch)
.unwrap(),
cc
);
// Loop through each validator in the committee.
for (committee_i, validator_i) in cc.committee.iter().enumerate() {
// Assert the validators are assigned contiguously across committees.
assert_eq!(
*validator_i,
*expected_indices_iter.next().unwrap(),
"Non-sequential validators."
);
// Assert a call to `get_attestation_duties` is consistent with a call to
// `get_crosslink_committees_at_slot`
let attestation_duty = state
.get_attestation_duties(*validator_i, relative_epoch)
.unwrap()
.unwrap();
assert_eq!(attestation_duty.slot, slot);
assert_eq!(attestation_duty.shard, cc.shard);
assert_eq!(attestation_duty.committee_index, committee_i);
assert_eq!(attestation_duty.committee_len, cc.committee.len());
}
}
}
// Assert that all validators were assigned to a committee.
assert!(expected_indices_iter.next().is_none());
// Assert that all shards were assigned to a committee.
assert!(expected_shards_iter.next().is_none());
}
fn committee_consistency_test<T: EthSpec>(
validator_count: usize,
state_epoch: Epoch,
cache_epoch: RelativeEpoch,
) {
let spec = &T::spec();
let mut builder = TestingBeaconStateBuilder::from_single_keypair(
validator_count,
&Keypair::random(),
spec,
);
let slot = state_epoch.start_slot(spec.slots_per_epoch);
builder.teleport_to_slot(slot, spec);
let (mut state, _keypairs): (BeaconState<T>, _) = builder.build();
let distinct_hashes: Vec<Hash256> = (0..T::latest_randao_mixes_length())
.into_iter()
.map(|i| Hash256::from(i as u64))
.collect();
state.latest_randao_mixes = FixedLenVec::from(distinct_hashes);
state
.build_committee_cache(RelativeEpoch::Previous, spec)
.unwrap();
state
.build_committee_cache(RelativeEpoch::Current, spec)
.unwrap();
state
.build_committee_cache(RelativeEpoch::Next, spec)
.unwrap();
let cache_epoch = cache_epoch.into_epoch(state_epoch);
execute_committee_consistency_test(state, cache_epoch, validator_count as usize, &spec);
}
fn committee_consistency_test_suite<T: EthSpec>(cached_epoch: RelativeEpoch) {
let spec = T::spec();
let validator_count = (T::shard_count() * spec.target_committee_size) + 1;
committee_consistency_test::<T>(validator_count as usize, Epoch::new(0), cached_epoch);
committee_consistency_test::<T>(
validator_count as usize,
spec.genesis_epoch + 4,
cached_epoch,
);
committee_consistency_test::<T>(
validator_count as usize,
spec.genesis_epoch + T::slots_per_historical_root() as u64 * T::slots_per_epoch() * 4,
cached_epoch,
);
}
#[test]
fn current_epoch_committee_consistency() {
committee_consistency_test_suite::<FewValidatorsEthSpec>(RelativeEpoch::Current);
}
#[test]
fn previous_epoch_committee_consistency() {
committee_consistency_test_suite::<FewValidatorsEthSpec>(RelativeEpoch::Previous);
}
#[test]
fn next_epoch_committee_consistency() {
committee_consistency_test_suite::<FewValidatorsEthSpec>(RelativeEpoch::Next);
}
}

View File

@ -1,63 +1,56 @@
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.1
/// Spec v0.6.1
pub enum Domain {
BeaconBlock,
BeaconProposer,
Randao,
Attestation,
Deposit,
Exit,
VoluntaryExit,
Transfer,
}
/// Holds all the "constants" for a BeaconChain.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(PartialEq, Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ChainSpec {
/*
* Misc
*/
pub shard_count: u64,
pub target_committee_size: u64,
pub max_balance_churn_quotient: u64,
pub max_indices_per_slashable_vote: usize,
pub max_exit_dequeues_per_epoch: u64,
pub target_committee_size: usize,
pub max_indices_per_attestation: u64,
pub min_per_epoch_churn_limit: u64,
pub churn_limit_quotient: u64,
pub base_rewards_per_epoch: u64,
pub shuffle_round_count: u8,
/*
* Deposit contract
*/
pub deposit_contract_address: Address,
pub deposit_contract_tree_depth: u64,
/*
* Gwei values
*/
pub min_deposit_amount: u64,
pub max_deposit_amount: u64,
pub fork_choice_balance_increment: u64,
pub max_effective_balance: u64,
pub ejection_balance: u64,
pub effective_balance_increment: u64,
/*
* Initial Values
*/
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,
@ -69,18 +62,21 @@ pub struct ChainSpec {
pub slots_per_epoch: u64,
pub min_seed_lookahead: Epoch,
pub activation_exit_delay: u64,
pub epochs_per_eth1_voting_period: u64,
pub slots_per_eth1_voting_period: u64,
pub slots_per_historical_root: usize,
pub min_validator_withdrawability_delay: Epoch,
pub persistent_committee_period: u64,
pub max_crosslink_epochs: u64,
pub min_epochs_to_inactivity_penalty: u64,
/*
* Reward and penalty quotients
*/
pub base_reward_quotient: u64,
pub whistleblower_reward_quotient: u64,
pub attestation_inclusion_reward_quotient: u64,
pub whistleblowing_reward_quotient: u64,
pub proposer_reward_quotient: u64,
pub inactivity_penalty_quotient: u64,
pub min_penalty_quotient: u64,
pub min_slashing_penalty_quotient: u64,
/*
* Max operations per block
@ -100,11 +96,11 @@ pub struct ChainSpec {
*
* Use `ChainSpec::get_domain(..)` to access these values.
*/
domain_beacon_block: u32,
domain_beacon_proposer: u32,
domain_randao: u32,
domain_attestation: u32,
domain_deposit: u32,
domain_exit: u32,
domain_voluntary_exit: u32,
domain_transfer: u32,
/*
@ -116,29 +112,16 @@ pub struct ChainSpec {
}
impl ChainSpec {
/// Return the number of committees in one epoch.
///
/// Spec v0.5.1
pub fn get_epoch_committee_count(&self, active_validator_count: usize) -> u64 {
std::cmp::max(
1,
std::cmp::min(
self.shard_count / self.slots_per_epoch,
active_validator_count as u64 / self.slots_per_epoch / self.target_committee_size,
),
) * self.slots_per_epoch
}
/// Get the domain number that represents the fork meta and signature domain.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn get_domain(&self, epoch: Epoch, domain: Domain, fork: &Fork) -> u64 {
let domain_constant = match domain {
Domain::BeaconBlock => self.domain_beacon_block,
Domain::BeaconProposer => self.domain_beacon_proposer,
Domain::Randao => self.domain_randao,
Domain::Attestation => self.domain_attestation,
Domain::Deposit => self.domain_deposit,
Domain::Exit => self.domain_exit,
Domain::VoluntaryExit => self.domain_voluntary_exit,
Domain::Transfer => self.domain_transfer,
};
@ -153,47 +136,39 @@ impl ChainSpec {
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub(crate) fn foundation() -> Self {
let genesis_slot = Slot::new(2_u64.pow(32));
let slots_per_epoch = 64;
let genesis_epoch = genesis_slot.epoch(slots_per_epoch);
Self {
/*
* Misc
*/
shard_count: 1_024,
target_committee_size: 128,
max_balance_churn_quotient: 32,
max_indices_per_slashable_vote: 4_096,
max_exit_dequeues_per_epoch: 4,
max_indices_per_attestation: 4096,
min_per_epoch_churn_limit: 4,
churn_limit_quotient: 65_536,
base_rewards_per_epoch: 5,
shuffle_round_count: 90,
/*
* Deposit contract
*/
deposit_contract_address: Address::zero(),
deposit_contract_tree_depth: 32,
/*
* Gwei values
*/
min_deposit_amount: u64::pow(2, 0) * GWEI,
max_deposit_amount: u64::pow(2, 5) * GWEI,
fork_choice_balance_increment: u64::pow(2, 0) * GWEI,
ejection_balance: u64::pow(2, 4) * GWEI,
min_deposit_amount: u64::pow(2, 0) * u64::pow(10, 9),
max_effective_balance: u64::pow(2, 5) * u64::pow(10, 9),
ejection_balance: u64::pow(2, 4) * u64::pow(10, 9),
effective_balance_increment: u64::pow(2, 0) * u64::pow(10, 9),
/*
* Initial Values
*/
genesis_fork_version: 0,
genesis_slot,
genesis_epoch,
genesis_start_shard: 0,
genesis_slot: Slot::new(0),
genesis_epoch: Epoch::new(0),
far_future_epoch: Epoch::new(u64::max_value()),
zero_hash: Hash256::zero(),
empty_signature: Signature::empty_signature(),
bls_withdrawal_prefix_byte: 0,
/*
@ -201,21 +176,24 @@ impl ChainSpec {
*/
seconds_per_slot: 6,
min_attestation_inclusion_delay: 4,
slots_per_epoch,
slots_per_epoch: 64,
min_seed_lookahead: Epoch::new(1),
activation_exit_delay: 4,
epochs_per_eth1_voting_period: 16,
slots_per_eth1_voting_period: 1_024,
slots_per_historical_root: 8_192,
min_validator_withdrawability_delay: Epoch::new(256),
persistent_committee_period: 2_048,
max_crosslink_epochs: 64,
min_epochs_to_inactivity_penalty: 4,
/*
* Reward and penalty quotients
*/
base_reward_quotient: 32,
whistleblower_reward_quotient: 512,
attestation_inclusion_reward_quotient: 8,
inactivity_penalty_quotient: 16_777_216,
min_penalty_quotient: 32,
whistleblowing_reward_quotient: 512,
proposer_reward_quotient: 8,
inactivity_penalty_quotient: 33_554_432,
min_slashing_penalty_quotient: 32,
/*
* Max operations per block
@ -225,16 +203,16 @@ impl ChainSpec {
max_attestations: 128,
max_deposits: 16,
max_voluntary_exits: 16,
max_transfers: 16,
max_transfers: 0,
/*
* Signature domains
*/
domain_beacon_block: 0,
domain_beacon_proposer: 0,
domain_randao: 1,
domain_attestation: 2,
domain_deposit: 3,
domain_exit: 4,
domain_voluntary_exit: 4,
domain_transfer: 5,
/*
@ -265,12 +243,11 @@ impl ChainSpec {
/// Returns a `ChainSpec` compatible with the specification suitable for 8 validators.
pub(crate) fn few_validators() -> Self {
let genesis_slot = Slot::new(2_u64.pow(32));
let genesis_slot = Slot::new(0);
let slots_per_epoch = 8;
let genesis_epoch = genesis_slot.epoch(slots_per_epoch);
Self {
shard_count: 8,
target_committee_size: 1,
genesis_slot,
genesis_epoch,
@ -312,11 +289,11 @@ mod tests {
fn test_get_domain() {
let spec = ChainSpec::foundation();
test_domain(Domain::BeaconBlock, spec.domain_beacon_block, &spec);
test_domain(Domain::BeaconProposer, spec.domain_beacon_proposer, &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::VoluntaryExit, spec.domain_voluntary_exit, &spec);
test_domain(Domain::Transfer, spec.domain_transfer, &spec);
}
}

View File

@ -8,11 +8,12 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// Specifies the block hash for a shard at an epoch.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Default,
Serialize,
Deserialize,
@ -25,6 +26,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
)]
pub struct Crosslink {
pub epoch: Epoch,
pub previous_crosslink_root: Hash256,
pub crosslink_data_root: Hash256,
}

View File

@ -1,21 +1,25 @@
use crate::*;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use tree_hash_derive::{CachedTreeHash, TreeHash};
#[derive(
Default,
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
Decode,
Encode,
TreeHash,
CachedTreeHash,
)]
pub struct CrosslinkCommittee {
#[derive(Default, Clone, Debug, PartialEq, TreeHash, CachedTreeHash)]
pub struct CrosslinkCommittee<'a> {
pub slot: Slot,
pub shard: Shard,
pub committee: &'a [usize],
}
impl<'a> CrosslinkCommittee<'a> {
pub fn into_owned(self) -> OwnedCrosslinkCommittee {
OwnedCrosslinkCommittee {
slot: self.slot,
shard: self.shard,
committee: self.committee.to_vec(),
}
}
}
#[derive(Default, Clone, Debug, PartialEq, TreeHash, CachedTreeHash)]
pub struct OwnedCrosslinkCommittee {
pub slot: Slot,
pub shard: Shard,
pub committee: Vec<usize>,

View File

@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// A deposit to potentially become a beacon chain validator.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
@ -25,7 +25,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
pub struct Deposit {
pub proof: FixedLenVec<Hash256, U32>,
pub index: u64,
pub deposit_data: DepositData,
pub data: DepositData,
}
#[cfg(test)]

View File

@ -1,14 +1,16 @@
use super::DepositInput;
use crate::test_utils::TestRandom;
use crate::*;
use bls::{PublicKey, Signature};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash_derive::{CachedTreeHash, TreeHash};
use tree_hash::{SignedRoot, TreeHash};
use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// Data generated by the deposit contract.
/// The data supplied by the user to the deposit contract.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
@ -17,14 +19,35 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
Deserialize,
Encode,
Decode,
SignedRoot,
TreeHash,
CachedTreeHash,
TestRandom,
)]
pub struct DepositData {
pub pubkey: PublicKey,
pub withdrawal_credentials: Hash256,
pub amount: u64,
pub timestamp: u64,
pub deposit_input: DepositInput,
#[signed_root(skip_hashing)]
pub signature: Signature,
}
impl DepositData {
/// Generate the signature for a given DepositData details.
///
/// Spec v0.6.1
pub fn create_signature(
&self,
secret_key: &SecretKey,
epoch: Epoch,
fork: &Fork,
spec: &ChainSpec,
) -> Signature {
let msg = self.signed_root();
let domain = spec.get_domain(epoch, Domain::Deposit, fork);
Signature::new(msg.as_slice(), domain, secret_key)
}
}
#[cfg(test)]

View File

@ -1,92 +0,0 @@
use crate::test_utils::TestRandom;
use crate::*;
use bls::{PublicKey, Signature};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash::{SignedRoot, TreeHash};
use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// The data supplied by the user to the deposit contract.
///
/// Spec v0.5.1
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
SignedRoot,
TreeHash,
CachedTreeHash,
TestRandom,
)]
pub struct DepositInput {
pub pubkey: PublicKey,
pub withdrawal_credentials: Hash256,
#[signed_root(skip_hashing)]
pub proof_of_possession: Signature,
}
impl DepositInput {
/// Generate the 'proof_of_posession' signature for a given DepositInput details.
///
/// Spec v0.5.1
pub fn create_proof_of_possession(
&self,
secret_key: &SecretKey,
epoch: Epoch,
fork: &Fork,
spec: &ChainSpec,
) -> Signature {
let msg = self.signed_root();
let domain = spec.get_domain(epoch, Domain::Deposit, fork);
Signature::new(msg.as_slice(), domain, secret_key)
}
/// Verify that proof-of-possession is valid.
///
/// Spec v0.5.1
pub fn validate_proof_of_possession(
&self,
epoch: Epoch,
fork: &Fork,
spec: &ChainSpec,
) -> bool {
let msg = self.signed_root();
let domain = spec.get_domain(epoch, Domain::Deposit, fork);
self.proof_of_possession.verify(&msg, domain, &self.pubkey)
}
}
#[cfg(test)]
mod tests {
use super::*;
ssz_tests!(DepositInput);
cached_tree_hash_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));
}
}

View File

@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// Contains data obtained from the Eth1 chain.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
@ -24,6 +24,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
)]
pub struct Eth1Data {
pub deposit_root: Hash256,
pub deposit_count: u64,
pub block_hash: Hash256,
}

View File

@ -1,36 +0,0 @@
use super::Eth1Data;
use crate::test_utils::TestRandom;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash_derive::{CachedTreeHash, TreeHash};
/// A summation of votes for some `Eth1Data`.
///
/// Spec v0.5.1
#[derive(
Debug,
PartialEq,
Clone,
Default,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
CachedTreeHash,
TestRandom,
)]
pub struct Eth1DataVote {
pub eth1_data: Eth1Data,
pub vote_count: u64,
}
#[cfg(test)]
mod tests {
use super::*;
ssz_tests!(Eth1DataVote);
cached_tree_hash_tests!(Eth1DataVote);
}

View File

@ -2,7 +2,6 @@ use crate::{
test_utils::{fork_from_hex_str, TestRandom},
ChainSpec, Epoch,
};
use int_to_bytes::int_to_bytes4;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@ -11,7 +10,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// Specifies a fork of the `BeaconChain`, to prevent replay attacks.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
Clone,
@ -36,21 +35,18 @@ pub struct Fork {
impl Fork {
/// Initialize the `Fork` from the genesis parameters in the `spec`.
///
/// Spec v0.5.1
/// Spec v0.6.1
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,
previous_version: [0; 4],
current_version: [0; 4],
epoch: spec.genesis_epoch,
}
}
/// Return the fork version of the given ``epoch``.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn get_fork_version(&self, epoch: Epoch) -> [u8; 4] {
if epoch < self.epoch {
return self.previous_version;
@ -66,10 +62,9 @@ mod tests {
ssz_tests!(Fork);
cached_tree_hash_tests!(Fork);
fn test_genesis(version: u32, epoch: Epoch) {
fn test_genesis(epoch: Epoch) {
let mut spec = ChainSpec::foundation();
spec.genesis_fork_version = version;
spec.genesis_epoch = epoch;
let fork = Fork::genesis(&spec);
@ -79,19 +74,14 @@ mod tests {
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_genesis(Epoch::new(0));
test_genesis(Epoch::new(11));
test_genesis(Epoch::new(2_u64.pow(63)));
test_genesis(Epoch::max_value());
}
#[test]

View File

@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// Historical block and state roots.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
Clone,

View File

@ -0,0 +1,135 @@
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// Details an attestation that can be slashable.
///
/// To be included in an `AttesterSlashing`.
///
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
CachedTreeHash,
TestRandom,
SignedRoot,
)]
pub struct IndexedAttestation {
/// Lists validator registry indices, not committee indices.
pub custody_bit_0_indices: Vec<u64>,
pub custody_bit_1_indices: Vec<u64>,
pub data: AttestationData,
#[signed_root(skip_hashing)]
pub signature: AggregateSignature,
}
impl IndexedAttestation {
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
///
/// Spec v0.6.1
pub fn is_double_vote(&self, other: &IndexedAttestation) -> bool {
self.data.target_epoch == other.data.target_epoch && self.data != other.data
}
/// Check if ``attestation_data_1`` surrounds ``attestation_data_2``.
///
/// Spec v0.6.1
pub fn is_surround_vote(&self, other: &IndexedAttestation) -> bool {
self.data.source_epoch < other.data.source_epoch
&& other.data.target_epoch < self.data.target_epoch
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::slot_epoch::Epoch;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_is_double_vote_true() {
let indexed_vote_first = create_indexed_attestation(3, 1);
let indexed_vote_second = create_indexed_attestation(3, 2);
assert_eq!(
indexed_vote_first.is_double_vote(&indexed_vote_second),
true
)
}
#[test]
pub fn test_is_double_vote_false() {
let indexed_vote_first = create_indexed_attestation(1, 1);
let indexed_vote_second = create_indexed_attestation(2, 1);
assert_eq!(
indexed_vote_first.is_double_vote(&indexed_vote_second),
false
);
}
#[test]
pub fn test_is_surround_vote_true() {
let indexed_vote_first = create_indexed_attestation(2, 1);
let indexed_vote_second = create_indexed_attestation(1, 2);
assert_eq!(
indexed_vote_first.is_surround_vote(&indexed_vote_second),
true
);
}
#[test]
pub fn test_is_surround_vote_true_realistic() {
let indexed_vote_first = create_indexed_attestation(4, 1);
let indexed_vote_second = create_indexed_attestation(3, 2);
assert_eq!(
indexed_vote_first.is_surround_vote(&indexed_vote_second),
true
);
}
#[test]
pub fn test_is_surround_vote_false_source_epoch_fails() {
let indexed_vote_first = create_indexed_attestation(2, 2);
let indexed_vote_second = create_indexed_attestation(1, 1);
assert_eq!(
indexed_vote_first.is_surround_vote(&indexed_vote_second),
false
);
}
#[test]
pub fn test_is_surround_vote_false_target_epoch_fails() {
let indexed_vote_first = create_indexed_attestation(1, 1);
let indexed_vote_second = create_indexed_attestation(2, 2);
assert_eq!(
indexed_vote_first.is_surround_vote(&indexed_vote_second),
false
);
}
ssz_tests!(IndexedAttestation);
cached_tree_hash_tests!(IndexedAttestation);
fn create_indexed_attestation(target_epoch: u64, source_epoch: u64) -> IndexedAttestation {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut indexed_vote = IndexedAttestation::random_for_test(&mut rng);
indexed_vote.data.source_epoch = Epoch::new(source_epoch);
indexed_vote.data.target_epoch = Epoch::new(target_epoch);
indexed_vote
}
}

View File

@ -17,15 +17,13 @@ pub mod crosslink;
pub mod crosslink_committee;
pub mod deposit;
pub mod deposit_data;
pub mod deposit_input;
pub mod eth1_data;
pub mod eth1_data_vote;
pub mod fork;
pub mod free_attestation;
pub mod historical_batch;
pub mod indexed_attestation;
pub mod pending_attestation;
pub mod proposer_slashing;
pub mod slashable_attestation;
pub mod transfer;
pub mod voluntary_exit;
#[macro_use]
@ -49,19 +47,17 @@ pub use crate::beacon_block_header::BeaconBlockHeader;
pub use crate::beacon_state::{Error as BeaconStateError, *};
pub use crate::chain_spec::{ChainSpec, Domain};
pub use crate::crosslink::Crosslink;
pub use crate::crosslink_committee::CrosslinkCommittee;
pub use crate::crosslink_committee::{CrosslinkCommittee, OwnedCrosslinkCommittee};
pub use crate::deposit::Deposit;
pub use crate::deposit_data::DepositData;
pub use crate::deposit_input::DepositInput;
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::indexed_attestation::IndexedAttestation;
pub use crate::pending_attestation::PendingAttestation;
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;
pub use crate::transfer::Transfer;
@ -85,7 +81,7 @@ 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 fixed_len_vec::{typenum::Unsigned, FixedLenVec};
pub use fixed_len_vec::{typenum, typenum::Unsigned, FixedLenVec};
pub use libp2p::floodsub::{Topic, TopicBuilder, TopicHash};
pub use libp2p::multiaddr;
pub use libp2p::Multiaddr;

View File

@ -1,5 +1,5 @@
use crate::test_utils::TestRandom;
use crate::{Attestation, AttestationData, Bitfield, Slot};
use crate::{AttestationData, Bitfield};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// An attestation that has been included in the state but not yet fully processed.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
Clone,
@ -24,20 +24,8 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
pub struct PendingAttestation {
pub aggregation_bitfield: Bitfield,
pub data: AttestationData,
pub custody_bitfield: Bitfield,
pub inclusion_slot: Slot,
}
impl PendingAttestation {
/// Create a `PendingAttestation` from an `Attestation`, at the given `inclusion_slot`.
pub fn from_attestation(attestation: &Attestation, inclusion_slot: Slot) -> Self {
PendingAttestation {
data: attestation.data.clone(),
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
custody_bitfield: attestation.custody_bitfield.clone(),
inclusion_slot,
}
}
pub inclusion_delay: u64,
pub proposer_index: u64,
}
#[cfg(test)]

View File

@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// Two conflicting proposals from the same proposer (validator).
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,

View File

@ -4,41 +4,32 @@ use crate::*;
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.1
/// Spec v0.6.1
#[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,
/// The next epoch.
Next,
}
impl RelativeEpoch {
/// Returns the `epoch` that `self` refers to, with respect to the `base` epoch.
///
/// Spec v0.5.1
/// Spec v0.6.1
pub fn into_epoch(self, base: Epoch) -> Epoch {
match self {
RelativeEpoch::Previous => base - 1,
// Due to saturating nature of epoch, check for current first.
RelativeEpoch::Current => base,
RelativeEpoch::NextWithoutRegistryChange => base + 1,
RelativeEpoch::NextWithRegistryChange => base + 1,
RelativeEpoch::Previous => base - 1,
RelativeEpoch::Next => base + 1,
}
}
@ -48,17 +39,16 @@ impl RelativeEpoch {
/// 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.1
/// Spec v0.6.1
pub fn from_epoch(base: Epoch, other: Epoch) -> Result<Self, Error> {
if other == base - 1 {
Ok(RelativeEpoch::Previous)
} else if other == base {
// Due to saturating nature of epoch, check for current first.
if other == base {
Ok(RelativeEpoch::Current)
} else if other == base - 1 {
Ok(RelativeEpoch::Previous)
} else if other == base + 1 {
Err(Error::AmbiguiousNextEpoch)
Ok(RelativeEpoch::Next)
} else if other < base {
Err(Error::EpochTooLow { base, other })
} else {
@ -67,11 +57,8 @@ impl RelativeEpoch {
}
/// 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),
)
pub fn from_slot(base: Slot, other: Slot, slots_per_epoch: u64) -> Result<Self, Error> {
Self::from_epoch(base.epoch(slots_per_epoch), other.epoch(slots_per_epoch))
}
}
@ -85,14 +72,7 @@ mod tests {
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
);
assert_eq!(RelativeEpoch::Next.into_epoch(base), base + 1);
}
#[test]
@ -109,26 +89,26 @@ mod tests {
);
assert_eq!(
RelativeEpoch::from_epoch(base, base + 1),
Err(RelativeEpochError::AmbiguiousNextEpoch)
Ok(RelativeEpoch::Next)
);
}
#[test]
fn from_slot() {
let spec = ChainSpec::foundation();
let base = Epoch::new(10).start_slot(spec.slots_per_epoch);
let slots_per_epoch: u64 = 64;
let base = Slot::new(10 * slots_per_epoch);
assert_eq!(
RelativeEpoch::from_slot(base, base - 1, &spec),
RelativeEpoch::from_slot(base, base - 1, slots_per_epoch),
Ok(RelativeEpoch::Previous)
);
assert_eq!(
RelativeEpoch::from_slot(base, base, &spec),
RelativeEpoch::from_slot(base, base, slots_per_epoch),
Ok(RelativeEpoch::Current)
);
assert_eq!(
RelativeEpoch::from_slot(base, base + spec.slots_per_epoch, &spec),
Err(RelativeEpochError::AmbiguiousNextEpoch)
RelativeEpoch::from_slot(base, base + slots_per_epoch, slots_per_epoch),
Ok(RelativeEpoch::Next)
);
}
}

View File

@ -1,151 +0,0 @@
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// Details an attestation that can be slashable.
///
/// To be included in an `AttesterSlashing`.
///
/// Spec v0.5.1
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
CachedTreeHash,
TestRandom,
SignedRoot,
)]
pub struct SlashableAttestation {
/// Lists validator registry indices, not committee indices.
pub validator_indices: Vec<u64>,
pub data: AttestationData,
pub custody_bitfield: Bitfield,
#[signed_root(skip_hashing)]
pub aggregate_signature: AggregateSignature,
}
impl SlashableAttestation {
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
///
/// Spec v0.5.1
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.5.1
pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
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);
(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_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(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_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(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_attestation(2, 1, &spec);
let slashable_vote_second = create_slashable_attestation(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_attestation(4, 1, &spec);
let slashable_vote_second = create_slashable_attestation(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_attestation(2, 2, &spec);
let slashable_vote_second = create_slashable_attestation(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_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(2, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
false
);
}
ssz_tests!(SlashableAttestation);
cached_tree_hash_tests!(SlashableAttestation);
fn create_slashable_attestation(
slot_factor: 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.source_epoch = Epoch::new(source_epoch);
slashable_vote
}
}

View File

@ -58,10 +58,12 @@ impl Epoch {
Epoch(u64::max_value())
}
/// The first slot in the epoch.
pub fn start_slot(self, slots_per_epoch: u64) -> Slot {
Slot::from(self.0.saturating_mul(slots_per_epoch))
}
/// The last slot in the epoch.
pub fn end_slot(self, slots_per_epoch: u64) -> Slot {
Slot::from(
self.0
@ -71,6 +73,20 @@ impl Epoch {
)
}
/// Position of some slot inside an epoch, if any.
///
/// E.g., the first `slot` in `epoch` is at position `0`.
pub fn position(&self, slot: Slot, slots_per_epoch: u64) -> Option<usize> {
let start = self.start_slot(slots_per_epoch);
let end = self.end_slot(slots_per_epoch);
if (slot >= start) && (slot <= end) {
Some(slot.as_usize() - start.as_usize())
} else {
None
}
}
pub fn slot_iter(&self, slots_per_epoch: u64) -> SlotIter {
SlotIter {
current_iteration: 0,
@ -124,6 +140,26 @@ mod epoch_tests {
assert_eq!(epoch.end_slot(slots_per_epoch), Slot::new(7));
}
#[test]
fn position() {
let slots_per_epoch = 8;
let epoch = Epoch::new(0);
assert_eq!(epoch.position(Slot::new(0), slots_per_epoch), Some(0));
assert_eq!(epoch.position(Slot::new(1), slots_per_epoch), Some(1));
assert_eq!(epoch.position(Slot::new(2), slots_per_epoch), Some(2));
assert_eq!(epoch.position(Slot::new(3), slots_per_epoch), Some(3));
assert_eq!(epoch.position(Slot::new(4), slots_per_epoch), Some(4));
assert_eq!(epoch.position(Slot::new(5), slots_per_epoch), Some(5));
assert_eq!(epoch.position(Slot::new(6), slots_per_epoch), Some(6));
assert_eq!(epoch.position(Slot::new(7), slots_per_epoch), Some(7));
assert_eq!(epoch.position(Slot::new(8), slots_per_epoch), None);
let epoch = Epoch::new(1);
assert_eq!(epoch.position(Slot::new(7), slots_per_epoch), None);
assert_eq!(epoch.position(Slot::new(8), slots_per_epoch), Some(0));
}
#[test]
fn slot_iter() {
let slots_per_epoch = 8;

View File

@ -0,0 +1,21 @@
mod testing_attestation_builder;
mod testing_attestation_data_builder;
mod testing_attester_slashing_builder;
mod testing_beacon_block_builder;
mod testing_beacon_state_builder;
mod testing_deposit_builder;
mod testing_pending_attestation_builder;
mod testing_proposer_slashing_builder;
mod testing_transfer_builder;
mod testing_voluntary_exit_builder;
pub use testing_attestation_builder::*;
pub use testing_attestation_data_builder::*;
pub use testing_attester_slashing_builder::*;
pub use testing_beacon_block_builder::*;
pub use testing_beacon_state_builder::*;
pub use testing_deposit_builder::*;
pub use testing_pending_attestation_builder::*;
pub use testing_proposer_slashing_builder::*;
pub use testing_transfer_builder::*;
pub use testing_voluntary_exit_builder::*;

View File

@ -33,7 +33,7 @@ impl TestingAttestationBuilder {
aggregation_bitfield,
data: data_builder.build(),
custody_bitfield,
aggregate_signature: AggregateSignature::new(),
signature: AggregateSignature::new(),
};
Self {
@ -77,13 +77,13 @@ impl TestingAttestationBuilder {
.tree_hash_root();
let domain = spec.get_domain(
self.attestation.data.slot.epoch(spec.slots_per_epoch),
self.attestation.data.target_epoch,
Domain::Attestation,
fork,
);
let signature = Signature::new(&message, domain, secret_keys[key_index]);
self.attestation.aggregate_signature.add(&signature)
self.attestation.signature.add(&signature)
}
}

View File

@ -1,4 +1,5 @@
use crate::*;
use tree_hash::TreeHash;
/// Builds an `AttestationData` to be used for testing purposes.
///
@ -16,8 +17,8 @@ impl TestingAttestationDataBuilder {
slot: Slot,
spec: &ChainSpec,
) -> Self {
let current_epoch = state.current_epoch(spec);
let previous_epoch = state.previous_epoch(spec);
let current_epoch = state.current_epoch();
let previous_epoch = state.previous_epoch();
let is_previous_epoch =
state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch);
@ -28,6 +29,12 @@ impl TestingAttestationDataBuilder {
state.current_justified_epoch
};
let target_epoch = if is_previous_epoch {
state.previous_epoch()
} else {
state.current_epoch()
};
let target_root = if is_previous_epoch {
*state
.get_block_root(previous_epoch.start_slot(spec.slots_per_epoch))
@ -38,26 +45,34 @@ impl TestingAttestationDataBuilder {
.unwrap()
};
let previous_crosslink_root = if is_previous_epoch {
Hash256::from_slice(
&state
.get_previous_crosslink(shard)
.unwrap()
.tree_hash_root(),
)
} else {
Hash256::from_slice(&state.get_current_crosslink(shard).unwrap().tree_hash_root())
};
let source_root = *state
.get_block_root(source_epoch.start_slot(spec.slots_per_epoch))
.unwrap();
let data = AttestationData {
// LMD GHOST vote
slot,
beacon_block_root: *state.get_block_root(slot).unwrap(),
// FFG Vote
source_epoch,
source_root,
target_epoch,
target_root,
// Crosslink vote
shard,
previous_crosslink: Crosslink {
epoch: slot.epoch(spec.slots_per_epoch),
crosslink_data_root: spec.zero_hash,
},
previous_crosslink_root,
crosslink_data_root: spec.zero_hash,
};

View File

@ -21,23 +21,20 @@ impl TestingAttesterSlashingBuilder {
where
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
{
let double_voted_slot = Slot::new(0);
let shard = 0;
let epoch = Epoch::new(0);
let epoch_1 = Epoch::new(1);
let epoch_2 = Epoch::new(2);
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_epoch: epoch_1,
source_root: hash_1,
target_epoch: epoch_2,
target_root: hash_1,
shard,
previous_crosslink: Crosslink {
epoch,
crosslink_data_root: hash_1,
},
previous_crosslink_root: hash_1,
crosslink_data_root: hash_1,
};
@ -46,21 +43,21 @@ impl TestingAttesterSlashingBuilder {
..data_1.clone()
};
let mut slashable_attestation_1 = SlashableAttestation {
validator_indices: validator_indices.to_vec(),
let mut attestation_1 = IndexedAttestation {
custody_bit_0_indices: validator_indices.to_vec(),
custody_bit_1_indices: vec![],
data: data_1,
custody_bitfield: Bitfield::new(),
aggregate_signature: AggregateSignature::new(),
signature: AggregateSignature::new(),
};
let mut slashable_attestation_2 = SlashableAttestation {
validator_indices: validator_indices.to_vec(),
let mut attestation_2 = IndexedAttestation {
custody_bit_0_indices: validator_indices.to_vec(),
custody_bit_1_indices: vec![],
data: data_2,
custody_bitfield: Bitfield::new(),
aggregate_signature: AggregateSignature::new(),
signature: AggregateSignature::new(),
};
let add_signatures = |attestation: &mut SlashableAttestation| {
let add_signatures = |attestation: &mut IndexedAttestation| {
// All validators sign with a `false` custody bit.
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
data: attestation.data.clone(),
@ -68,19 +65,19 @@ impl TestingAttesterSlashingBuilder {
};
let message = attestation_data_and_custody_bit.tree_hash_root();
for (i, validator_index) in validator_indices.iter().enumerate() {
attestation.custody_bitfield.set(i, false);
let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation);
attestation.aggregate_signature.add(&signature);
for validator_index in validator_indices {
let signature =
signer(*validator_index, &message[..], epoch_2, Domain::Attestation);
attestation.signature.add(&signature);
}
};
add_signatures(&mut slashable_attestation_1);
add_signatures(&mut slashable_attestation_2);
add_signatures(&mut attestation_1);
add_signatures(&mut attestation_2);
AttesterSlashing {
slashable_attestation_1,
slashable_attestation_2,
attestation_1,
attestation_2,
}
}
}

View File

@ -39,7 +39,7 @@ impl TestingBeaconBlockBuilder {
pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let message = self.block.signed_root();
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
let domain = spec.get_domain(epoch, Domain::BeaconBlock, fork);
let domain = spec.get_domain(epoch, Domain::BeaconProposer, fork);
self.block.signature = Signature::new(&message, domain, sk);
}
@ -119,15 +119,15 @@ impl TestingBeaconBlockBuilder {
break;
}
for crosslink_committee in state.get_crosslink_committees_at_slot(slot, spec)? {
for crosslink_committee in state.get_crosslink_committees_at_slot(slot)? {
if attestations_added >= num_attestations {
break;
}
committees.push((
slot,
crosslink_committee.committee.clone(),
crosslink_committee.committee.clone(),
crosslink_committee.committee.to_vec(),
crosslink_committee.committee.to_vec(),
crosslink_committee.shard,
));

View File

@ -1,4 +1,4 @@
use super::{generate_deterministic_keypairs, KeypairsFile};
use super::super::{generate_deterministic_keypairs, KeypairsFile};
use crate::test_utils::TestingPendingAttestationBuilder;
use crate::*;
use bls::get_withdrawal_credentials;
@ -95,6 +95,7 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
/// Creates the builder from an existing set of keypairs.
pub fn from_keypairs(keypairs: Vec<Keypair>, spec: &ChainSpec) -> Self {
let validator_count = keypairs.len();
let starting_balance = 32_000_000_000;
debug!(
"Building {} Validator objects from keypairs...",
@ -112,11 +113,12 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
pubkey: keypair.pk.clone(),
withdrawal_credentials,
// All validators start active.
activation_eligibility_epoch: spec.genesis_epoch,
activation_epoch: spec.genesis_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
initiated_exit: false,
slashed: false,
effective_balance: starting_balance,
}
})
.collect();
@ -137,16 +139,17 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
genesis_time,
Eth1Data {
deposit_root: Hash256::zero(),
deposit_count: 0,
block_hash: Hash256::zero(),
},
spec,
);
let balances = vec![32_000_000_000; validator_count];
let balances = vec![starting_balance; validator_count];
debug!("Importing {} existing validators...", validator_count);
state.validator_registry = validators;
state.validator_balances = balances;
state.balances = balances;
debug!("BeaconState initialized.");
@ -163,14 +166,7 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
/// Note: this performs the build when called. Ensure that no changes are made that would
/// invalidate this cache.
pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
let state = &mut self.state;
state.build_epoch_cache(RelativeEpoch::Previous, &spec)?;
state.build_epoch_cache(RelativeEpoch::Current, &spec)?;
state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)?;
state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)?;
state.update_pubkey_cache()?;
self.state.build_all_caches(spec).unwrap();
Ok(())
}
@ -192,18 +188,13 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
state.slot = slot;
state.previous_shuffling_epoch = epoch - 1;
state.current_shuffling_epoch = epoch;
state.previous_shuffling_seed = Hash256::from_low_u64_le(0);
state.current_shuffling_seed = Hash256::from_low_u64_le(1);
// FIXME(sproul): update latest_start_shard?
state.previous_justified_epoch = epoch - 3;
state.current_justified_epoch = epoch - 2;
state.justification_bitfield = u64::max_value();
state.finalized_epoch = epoch - 3;
state.validator_registry_update_epoch = epoch - 3;
}
/// Creates a full set of attestations for the `BeaconState`. Each attestation has full
@ -214,14 +205,14 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
let state = &mut self.state;
state
.build_epoch_cache(RelativeEpoch::Previous, spec)
.build_committee_cache(RelativeEpoch::Previous, spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::Current, spec)
.build_committee_cache(RelativeEpoch::Current, spec)
.unwrap();
let current_epoch = state.current_epoch(spec);
let previous_epoch = state.previous_epoch(spec);
let current_epoch = state.current_epoch();
let previous_epoch = state.previous_epoch();
let first_slot = previous_epoch.start_slot(spec.slots_per_epoch).as_u64();
let last_slot = current_epoch.end_slot(spec.slots_per_epoch).as_u64()
@ -231,10 +222,12 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
for slot in first_slot..=last_slot {
let slot = Slot::from(slot);
let committees = state
.get_crosslink_committees_at_slot(slot, spec)
let committees: Vec<OwnedCrosslinkCommittee> = state
.get_crosslink_committees_at_slot(slot)
.unwrap()
.clone();
.into_iter()
.map(|c| c.clone().into_owned())
.collect();
for crosslink_committee in committees {
let mut builder = TestingPendingAttestationBuilder::new(
@ -248,7 +241,7 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
builder.add_committee_participation(signers);
let attestation = builder.build();
if attestation.data.slot.epoch(spec.slots_per_epoch) < state.current_epoch(spec) {
if attestation.data.target_epoch < state.current_epoch() {
state.previous_epoch_attestations.push(attestation)
} else {
state.current_epoch_attestations.push(attestation)

View File

@ -14,14 +14,11 @@ impl TestingDepositBuilder {
let deposit = Deposit {
proof: vec![].into(),
index: 0,
deposit_data: DepositData {
data: DepositData {
pubkey,
withdrawal_credentials: Hash256::zero(),
amount,
timestamp: 1,
deposit_input: DepositInput {
pubkey,
withdrawal_credentials: Hash256::zero(),
proof_of_possession: Signature::empty_signature(),
},
signature: Signature::empty_signature(),
},
};
@ -43,17 +40,13 @@ impl TestingDepositBuilder {
&get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..],
);
self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone();
self.deposit
.deposit_data
.deposit_input
.withdrawal_credentials = withdrawal_credentials;
self.deposit.data.pubkey = keypair.pk.clone();
self.deposit.data.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);
self.deposit.data.signature =
self.deposit
.data
.create_signature(&keypair.sk, epoch, fork, spec);
}
/// Builds the deposit, consuming the builder.

View File

@ -11,8 +11,7 @@ pub struct TestingPendingAttestationBuilder {
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 `inclusion_delay` will be set to `MIN_ATTESTATION_INCLUSION_DELAY`.
///
/// * The aggregation and custody bitfields will all be empty, they need to be set with
/// `Self::add_committee_participation`.
@ -27,8 +26,9 @@ impl TestingPendingAttestationBuilder {
let pending_attestation = PendingAttestation {
aggregation_bitfield: Bitfield::new(),
data: data_builder.build(),
custody_bitfield: Bitfield::new(),
inclusion_slot: slot + spec.min_attestation_inclusion_delay,
inclusion_delay: spec.min_attestation_inclusion_delay,
// FIXME(sproul)
proposer_index: 0,
};
Self {
@ -42,15 +42,12 @@ impl TestingPendingAttestationBuilder {
/// `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.

View File

@ -41,13 +41,13 @@ impl TestingProposerSlashingBuilder {
header_1.signature = {
let message = header_1.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch);
signer(proposer_index, &message[..], epoch, Domain::BeaconBlock)
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
};
header_2.signature = {
let message = header_2.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch);
signer(proposer_index, &message[..], epoch, Domain::BeaconBlock)
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
};
ProposerSlashing {

View File

@ -25,7 +25,7 @@ impl TestingVoluntaryExitBuilder {
/// The signing secret key must match that of the exiting validator.
pub fn sign(&mut self, secret_key: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let message = self.exit.signed_root();
let domain = spec.get_domain(self.exit.epoch, Domain::Exit, fork);
let domain = spec.get_domain(self.exit.epoch, Domain::VoluntaryExit, fork);
self.exit.signature = Signature::new(&message, domain, secret_key);
}

View File

@ -1,20 +1,12 @@
#[macro_use]
mod macros;
mod builders;
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;
pub use builders::*;
pub use generate_deterministic_keypairs::generate_deterministic_keypair;
pub use generate_deterministic_keypairs::generate_deterministic_keypairs;
pub use keypairs_file::KeypairsFile;
@ -22,15 +14,5 @@ pub use rand::{
RngCore,
{prng::XorShiftRng, SeedableRng},
};
pub use serde_utils::{fork_from_hex_str, u8_from_hex_str};
pub use serde_utils::{fork_from_hex_str, graffiti_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;

View File

@ -2,6 +2,7 @@ use serde::de::Error;
use serde::{Deserialize, Deserializer};
pub const FORK_BYTES_LEN: usize = 4;
pub const GRAFFITI_BYTES_LEN: usize = 32;
pub fn u8_from_hex_str<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
@ -32,3 +33,24 @@ where
}
Ok(array)
}
pub fn graffiti_from_hex_str<'de, D>(deserializer: D) -> Result<[u8; GRAFFITI_BYTES_LEN], D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let mut array = [0 as u8; GRAFFITI_BYTES_LEN];
let decoded: Vec<u8> = hex::decode(&s.as_str()[2..]).map_err(D::Error::custom)?;
if decoded.len() > GRAFFITI_BYTES_LEN {
return Err(D::Error::custom("Fork length too long"));
}
for (i, item) in array.iter_mut().enumerate() {
if i > decoded.len() {
break;
}
*item = decoded[i];
}
Ok(array)
}

View File

@ -81,3 +81,4 @@ macro_rules! impl_test_random_for_u8_array {
}
impl_test_random_for_u8_array!(4);
impl_test_random_for_u8_array!(32);

View File

@ -11,7 +11,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// The data submitted to the deposit contract.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
Clone,

View File

@ -7,7 +7,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
/// Information about a `BeaconChain` validator.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
Clone,
@ -23,11 +23,12 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
pub struct Validator {
pub pubkey: PublicKey,
pub withdrawal_credentials: Hash256,
pub activation_eligibility_epoch: Epoch,
pub activation_epoch: Epoch,
pub exit_epoch: Epoch,
pub withdrawable_epoch: Epoch,
pub initiated_exit: bool,
pub slashed: bool,
pub effective_balance: u64,
}
impl Validator {
@ -36,6 +37,11 @@ impl Validator {
self.activation_epoch <= epoch && epoch < self.exit_epoch
}
/// Returns `true` if the validator is slashable at some epoch.
pub fn is_slashable_at(&self, epoch: Epoch) -> bool {
!self.slashed && self.activation_epoch <= epoch && epoch < self.withdrawable_epoch
}
/// Returns `true` if the validator is considered exited at some epoch.
pub fn is_exited_at(&self, epoch: Epoch) -> bool {
self.exit_epoch <= epoch
@ -43,7 +49,7 @@ impl Validator {
/// Returns `true` if the validator is able to withdraw at some epoch.
pub fn is_withdrawable_at(&self, epoch: Epoch) -> bool {
self.withdrawable_epoch <= epoch
epoch >= self.withdrawable_epoch
}
}
@ -53,11 +59,12 @@ impl Default for Validator {
Self {
pubkey: PublicKey::default(),
withdrawal_credentials: Hash256::default(),
activation_eligibility_epoch: Epoch::from(std::u64::MAX),
activation_epoch: Epoch::from(std::u64::MAX),
exit_epoch: Epoch::from(std::u64::MAX),
withdrawable_epoch: Epoch::from(std::u64::MAX),
initiated_exit: false,
slashed: false,
effective_balance: std::u64::MAX,
}
}
}
@ -75,7 +82,6 @@ mod tests {
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);
}

View File

@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
/// An exit voluntarily submitted a validator who wishes to withdraw.
///
/// Spec v0.5.1
/// Spec v0.6.1
#[derive(
Debug,
PartialEq,

View File

@ -5,10 +5,11 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.6.1" }
milagro_bls = { git = "https://github.com/sigp/milagro_bls", tag = "v0.9.0" }
cached_tree_hash = { path = "../cached_tree_hash" }
hashing = { path = "../hashing" }
hex = "0.3"
rand = "^0.5"
serde = "1.0"
serde_derive = "1.0"
serde_hex = { path = "../serde_hex" }

View File

@ -1,5 +1,5 @@
use super::PublicKey;
use bls_aggregates::AggregatePublicKey as RawAggregatePublicKey;
use milagro_bls::AggregatePublicKey as RawAggregatePublicKey;
/// A BLS aggregate public key.
///

View File

@ -1,8 +1,8 @@
use super::*;
use bls_aggregates::{
use cached_tree_hash::cached_tree_hash_ssz_encoding_as_vector;
use milagro_bls::{
AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature,
};
use cached_tree_hash::cached_tree_hash_ssz_encoding_as_vector;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use serde_hex::{encode as hex_encode, HexVisitor};

View File

@ -0,0 +1,36 @@
use super::{PublicKey, BLS_PUBLIC_KEY_BYTE_SIZE};
use bls_aggregates::AggregatePublicKey as RawAggregatePublicKey;
/// A BLS aggregate public key.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, Clone, Default)]
pub struct FakeAggregatePublicKey {
bytes: Vec<u8>,
}
impl FakeAggregatePublicKey {
pub fn new() -> Self {
Self::zero()
}
/// Creates a new all-zero's aggregate public key
pub fn zero() -> Self {
Self {
bytes: vec![0; BLS_PUBLIC_KEY_BYTE_SIZE],
}
}
pub fn add(&mut self, _public_key: &PublicKey) {
// No nothing.
}
pub fn as_raw(&self) -> &FakeAggregatePublicKey {
&self
}
pub fn as_bytes(&self) -> Vec<u8> {
self.bytes.clone()
}
}

View File

@ -1,4 +1,7 @@
use super::{fake_signature::FakeSignature, AggregatePublicKey, BLS_AGG_SIG_BYTE_SIZE};
use super::{
fake_aggregate_public_key::FakeAggregatePublicKey, fake_signature::FakeSignature,
BLS_AGG_SIG_BYTE_SIZE,
};
use cached_tree_hash::cached_tree_hash_ssz_encoding_as_vector;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
@ -43,7 +46,7 @@ impl FakeAggregateSignature {
&self,
_msg: &[u8],
_domain: u64,
_aggregate_public_key: &AggregatePublicKey,
_aggregate_public_key: &FakeAggregatePublicKey,
) -> bool {
true
}
@ -53,7 +56,7 @@ impl FakeAggregateSignature {
&self,
_messages: &[&[u8]],
_domain: u64,
_aggregate_public_keys: &[&AggregatePublicKey],
_aggregate_public_keys: &[&FakeAggregatePublicKey],
) -> bool {
true
}

View File

@ -0,0 +1,169 @@
use super::{SecretKey, BLS_PUBLIC_KEY_BYTE_SIZE};
use bls_aggregates::PublicKey as RawPublicKey;
use cached_tree_hash::cached_tree_hash_ssz_encoding_as_vector;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use serde_hex::{encode as hex_encode, HexVisitor};
use ssz::{ssz_encode, Decode, DecodeError};
use std::default;
use std::fmt;
use std::hash::{Hash, Hasher};
use tree_hash::tree_hash_ssz_encoding_as_vector;
/// A single BLS signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, Clone, Eq)]
pub struct FakePublicKey {
bytes: Vec<u8>,
}
impl FakePublicKey {
pub fn from_secret_key(_secret_key: &SecretKey) -> Self {
Self::zero()
}
/// Creates a new all-zero's public key
pub fn zero() -> Self {
Self {
bytes: vec![0; BLS_PUBLIC_KEY_BYTE_SIZE],
}
}
/// Returns the underlying point as compressed bytes.
///
/// Identical to `self.as_uncompressed_bytes()`.
pub fn as_bytes(&self) -> Vec<u8> {
self.bytes.clone()
}
/// Converts compressed bytes to FakePublicKey
pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
Ok(Self {
bytes: bytes.to_vec(),
})
}
/// Returns the FakePublicKey as (x, y) bytes
pub fn as_uncompressed_bytes(&self) -> Vec<u8> {
self.as_bytes()
}
/// Converts (x, y) bytes to FakePublicKey
pub fn from_uncompressed_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
Self::from_bytes(bytes)
}
/// Returns the last 6 bytes of the SSZ encoding of the public key, as a hex string.
///
/// Useful for providing a short identifier to the user.
pub fn concatenated_hex_id(&self) -> String {
let bytes = ssz_encode(self);
let end_bytes = &bytes[bytes.len().saturating_sub(6)..bytes.len()];
hex_encode(end_bytes)
}
// Returns itself
pub fn as_raw(&self) -> &Self {
self
}
}
impl fmt::Display for FakePublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.concatenated_hex_id())
}
}
impl default::Default for FakePublicKey {
fn default() -> Self {
let secret_key = SecretKey::random();
FakePublicKey::from_secret_key(&secret_key)
}
}
impl_ssz!(FakePublicKey, BLS_PUBLIC_KEY_BYTE_SIZE, "FakePublicKey");
impl Serialize for FakePublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&hex_encode(self.as_bytes()))
}
}
impl<'de> Deserialize<'de> for FakePublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = deserializer.deserialize_str(HexVisitor)?;
let pubkey = Self::from_ssz_bytes(&bytes[..])
.map_err(|e| serde::de::Error::custom(format!("invalid pubkey ({:?})", e)))?;
Ok(pubkey)
}
}
tree_hash_ssz_encoding_as_vector!(FakePublicKey);
cached_tree_hash_ssz_encoding_as_vector!(FakePublicKey, 48);
impl PartialEq for FakePublicKey {
fn eq(&self, other: &FakePublicKey) -> bool {
ssz_encode(self) == ssz_encode(other)
}
}
impl Hash for FakePublicKey {
/// Note: this is distinct from consensus serialization, it will produce a different hash.
///
/// This method uses the uncompressed bytes, which are much faster to obtain than the
/// compressed bytes required for consensus serialization.
///
/// Use `ssz::Encode` to obtain the bytes required for consensus hashing.
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_uncompressed_bytes().hash(state)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ssz::ssz_encode;
use tree_hash::TreeHash;
#[test]
pub fn test_ssz_round_trip() {
let sk = SecretKey::random();
let original = FakePublicKey::from_secret_key(&sk);
let bytes = ssz_encode(&original);
let decoded = FakePublicKey::from_ssz_bytes(&bytes).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_cached_tree_hash() {
let sk = SecretKey::random();
let original = FakePublicKey::from_secret_key(&sk);
let mut cache = cached_tree_hash::TreeHashCache::new(&original).unwrap();
assert_eq!(
cache.tree_hash_root().unwrap().to_vec(),
original.tree_hash_root()
);
let sk = SecretKey::random();
let modified = FakePublicKey::from_secret_key(&sk);
cache.update(&modified).unwrap();
assert_eq!(
cache.tree_hash_root().unwrap().to_vec(),
modified.tree_hash_root()
);
}
}

Some files were not shown because too many files have changed in this diff Show More