Update to Spec v0.10 (#817)

* Start updating types

* WIP

* Signature hacking

* Existing EF tests passing with fake_crypto

* Updates

* Delete outdated API spec

* The refactor continues

* It compiles

* WIP test fixes

* All release tests passing bar genesis state parsing

* Update and test YamlConfig

* Update to spec v0.10 compatible BLS

* Updates to BLS EF tests

* Add EF test for AggregateVerify

And delete unused hash2curve tests for uncompressed points

* Update EF tests to v0.10.1

* Use optional block root correctly in block proc

* Use genesis fork in deposit domain. All tests pass

* Cargo fmt

* Fast aggregate verify test

* Update REST API docs

* Cargo fmt

* Fix unused import

* Bump spec tags to v0.10.1

* Add `seconds_per_eth1_block` to chainspec

* Update to timestamp based eth1 voting scheme

* Return None from `get_votes_to_consider` if block cache is empty

* Handle overflows in `is_candidate_block`

* Revert to failing tests

* Fix eth1 data sets test

* Choose default vote according to spec

* Fix collect_valid_votes tests

* Fix `get_votes_to_consider` to choose all eligible blocks

* Uncomment winning_vote tests

* Add comments; remove unused code

* Reduce seconds_per_eth1_block for simulation

* Addressed review comments

* Add test for default vote case

* Fix logs

* Remove unused functions

* Meter default eth1 votes

* Fix comments

* Address review comments; remove unused dependency

* Disable/delete two outdated tests

* Bump eth1 default vote warn to error

* Delete outdated eth1 test

Co-authored-by: Pawan Dhananjay <pawandhananjay@gmail.com>
This commit is contained in:
Michael Sproul
2020-02-11 10:19:36 +11:00
committed by GitHub
parent 03e77390a3
commit 371e5adcf8
145 changed files with 1666 additions and 4437 deletions

View File

@@ -166,9 +166,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.store
.get_block(&beacon_block_root)?
.ok_or_else(|| Error::MissingBeaconBlock(beacon_block_root))?;
let beacon_state_root = beacon_block.state_root;
let beacon_state_root = beacon_block.state_root();
let beacon_state = self
.get_state(&beacon_state_root, Some(beacon_block.slot))?
.get_state(&beacon_state_root, Some(beacon_block.slot()))?
.ok_or_else(|| Error::MissingBeaconState(beacon_state_root))?;
CheckPoint {
@@ -216,39 +216,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map(|slot| slot.epoch(T::EthSpec::slots_per_epoch()))
}
/// Returns the beacon block body for each beacon block root in `roots`.
///
/// Fails if any root in `roots` does not have a corresponding block.
pub fn get_block_bodies(
&self,
roots: &[Hash256],
) -> Result<Vec<BeaconBlockBody<T::EthSpec>>, Error> {
let bodies: Result<Vec<_>, _> = roots
.iter()
.map(|root| match self.block_at_root(*root)? {
Some(block) => Ok(block.body),
None => Err(Error::DBInconsistent(format!("Missing block: {}", root))),
})
.collect();
Ok(bodies?)
}
/// Returns the beacon block header for each beacon block root in `roots`.
///
/// Fails if any root in `roots` does not have a corresponding block.
pub fn get_block_headers(&self, roots: &[Hash256]) -> Result<Vec<BeaconBlockHeader>, Error> {
let headers: Result<Vec<BeaconBlockHeader>, _> = roots
.iter()
.map(|root| match self.block_at_root(*root)? {
Some(block) => Ok(block.block_header()),
None => Err(Error::DBInconsistent("Missing block".into())),
})
.collect();
Ok(headers?)
}
/// Iterates across all `(block_root, slot)` pairs from the head of the chain (inclusive) to
/// the earliest reachable ancestor (may or may not be genesis).
///
@@ -268,7 +235,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let iter = BlockRootsIterator::owned(self.store.clone(), head.beacon_state);
Ok(ReverseBlockRootIterator::new(
(head.beacon_block_root, head.beacon_block.slot),
(head.beacon_block_root, head.beacon_block.slot()),
iter,
))
}
@@ -305,11 +272,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.get_block(&block_root)?
.ok_or_else(|| Error::MissingBeaconBlock(block_root))?;
let state = self
.get_state(&block.state_root, Some(block.slot))?
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
.get_state(&block.state_root(), Some(block.slot()))?
.ok_or_else(|| Error::MissingBeaconState(block.state_root()))?;
let iter = BlockRootsIterator::owned(self.store.clone(), state);
Ok(ReverseBlockRootIterator::new(
(block_root, block.slot),
(block_root, block.slot()),
iter,
))
}
@@ -349,24 +316,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
))
}
/// Returns the block at the given root, if any.
///
/// ## Errors
///
/// May return a database error.
pub fn block_at_root(
&self,
block_root: Hash256,
) -> Result<Option<BeaconBlock<T::EthSpec>>, Error> {
Ok(self.store.get(&block_root)?)
}
/// Returns the block at the given slot, if any. Only returns blocks in the canonical chain.
///
/// ## Errors
///
/// May return a database error.
pub fn block_at_slot(&self, slot: Slot) -> Result<Option<BeaconBlock<T::EthSpec>>, Error> {
pub fn block_at_slot(
&self,
slot: Slot,
) -> Result<Option<SignedBeaconBlock<T::EthSpec>>, Error> {
let root = self
.rev_iter_block_roots()?
.find(|(_, this_slot)| *this_slot == slot)
@@ -387,7 +345,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn get_block(
&self,
block_root: &Hash256,
) -> Result<Option<BeaconBlock<T::EthSpec>>, Error> {
) -> Result<Option<SignedBeaconBlock<T::EthSpec>>, Error> {
Ok(self.store.get_block(block_root)?)
}
@@ -448,7 +406,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.ok_or_else(|| Error::CanonicalHeadLockTimeout)?;
Ok(HeadInfo {
slot: head.beacon_block.slot,
slot: head.beacon_block.slot(),
block_root: head.beacon_block_root,
state_root: head.beacon_state_root,
finalized_checkpoint: head.beacon_state.finalized_checkpoint.clone(),
@@ -550,7 +508,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn best_slot(&self) -> Result<Slot, Error> {
self.canonical_head
.try_read_for(HEAD_LOCK_TIMEOUT)
.map(|head| head.beacon_block.slot)
.map(|head| head.beacon_block.slot())
.ok_or_else(|| Error::CanonicalHeadLockTimeout)
}
@@ -576,19 +534,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map(|(root, _slot)| root))
}
/// Reads the slot clock (see `self.read_slot_clock()` and returns the number of slots since
/// genesis.
pub fn slots_since_genesis(&self) -> Option<SlotHeight> {
let now = self.slot().ok()?;
let genesis_slot = self.spec.genesis_slot;
if now < genesis_slot {
None
} else {
Some(SlotHeight::from(now.as_u64() - genesis_slot.as_u64()))
}
}
/// Returns the block proposer for a given slot.
///
/// Information is read from the present `beacon_state` shuffling, only information from the
@@ -669,7 +614,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let data = self.produce_attestation_data_for_block(
index,
head.beacon_block_root,
head.beacon_block.slot,
head.beacon_block.slot(),
&state,
)?;
@@ -696,7 +641,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.produce_attestation_data_for_block(
index,
head.beacon_block_root,
head.beacon_block.slot,
head.beacon_block.slot(),
&state,
)
}
@@ -859,22 +804,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// the attestation points to a block in a prior epoch, then it is necessary to
// load the full state corresponding to its block, and transition it to the
// attestation's epoch.
let attestation_block_root = attestation_head_block.state_root();
let attestation_epoch = attestation.data.target.epoch;
let slots_per_epoch = T::EthSpec::slots_per_epoch();
let mut state = if attestation_epoch
== attestation_head_block.slot.epoch(slots_per_epoch)
== attestation_head_block.slot().epoch(slots_per_epoch)
{
self.store
.load_epoch_boundary_state(&attestation_head_block.state_root)?
.ok_or_else(|| Error::MissingBeaconState(attestation_head_block.state_root))?
.load_epoch_boundary_state(&attestation_block_root)?
.ok_or_else(|| Error::MissingBeaconState(attestation_block_root))?
} else {
let mut state = self
.store
.get_state(
&attestation_head_block.state_root,
Some(attestation_head_block.slot),
)?
.ok_or_else(|| Error::MissingBeaconState(attestation_head_block.state_root))?;
.get_state(&attestation_block_root, Some(attestation_head_block.slot()))?
.ok_or_else(|| Error::MissingBeaconState(attestation_block_root))?;
// Fastforward the state to the epoch in which the attestation was made.
// NOTE: this looks like a potential DoS vector, we should probably limit
@@ -902,7 +845,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.process_attestation_for_state_and_block(
attestation,
&state,
&attestation_head_block,
&attestation_head_block.message,
)
}
} else {
@@ -1045,7 +988,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
/// Accept some exit and queue it for inclusion in an appropriate block.
pub fn process_voluntary_exit(&self, exit: VoluntaryExit) -> Result<(), ExitValidationError> {
pub fn process_voluntary_exit(
&self,
exit: SignedVoluntaryExit,
) -> Result<(), ExitValidationError> {
match self.wall_clock_state() {
Ok(state) => self.op_pool.insert_voluntary_exit(exit, &state, &self.spec),
Err(e) => {
@@ -1109,7 +1055,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Will accept blocks from prior slots, however it will reject any block from a future slot.
pub fn process_block(
&self,
block: BeaconBlock<T::EthSpec>,
block: SignedBeaconBlock<T::EthSpec>,
) -> Result<BlockProcessingOutcome, Error> {
let outcome = self.process_block_internal(block.clone());
@@ -1120,7 +1066,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.log,
"Beacon block imported";
"block_root" => format!("{:?}", block_root),
"block_slot" => format!("{:?}", block.slot.as_u64()),
"block_slot" => format!("{:?}", block.slot().as_u64()),
);
let _ = self.event_handler.register(EventKind::BeaconBlockImported {
block_root: *block_root,
@@ -1160,11 +1106,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Will accept blocks from prior slots, however it will reject any block from a future slot.
fn process_block_internal(
&self,
block: BeaconBlock<T::EthSpec>,
signed_block: SignedBeaconBlock<T::EthSpec>,
) -> Result<BlockProcessingOutcome, Error> {
metrics::inc_counter(&metrics::BLOCK_PROCESSING_REQUESTS);
let full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES);
let block = &signed_block.message;
let finalized_slot = self
.head_info()?
.finalized_checkpoint
@@ -1246,9 +1194,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// Load the parent blocks state from the database, returning an error if it is not found.
// It is an error because if we know the parent block we should also know the parent state.
let parent_state_root = parent_block.state_root;
let parent_state_root = parent_block.state_root();
let parent_state = self
.get_state(&parent_state_root, Some(parent_block.slot))?
.get_state(&parent_state_root, Some(parent_block.slot()))?
.ok_or_else(|| {
Error::DBInconsistent(format!("Missing state {:?}", parent_state_root))
})?;
@@ -1268,7 +1216,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let distance = block.slot.as_u64().saturating_sub(state.slot.as_u64());
for i in 0..distance {
let state_root = if i == 0 {
parent_block.state_root
parent_block.state_root()
} else {
// This is a new state we've reached, so stage it for storage in the DB.
// Computing the state root here is time-equivalent to computing it during slot
@@ -1302,7 +1250,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// slot).
match per_block_processing(
&mut state,
&block,
&signed_block,
Some(block_root),
BlockSignatureStrategy::VerifyBulk,
&self.spec,
@@ -1372,7 +1320,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// settles down).
// See: https://github.com/sigp/lighthouse/issues/692
self.store.put_state(&state_root, state)?;
self.store.put_block(&block_root, block)?;
self.store.put_block(&block_root, signed_block)?;
metrics::stop_timer(db_write_timer);
@@ -1450,26 +1398,28 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.deposits_for_block_inclusion(&state, &eth1_data, &self.spec)?
.into();
let mut block = BeaconBlock {
slot: state.slot,
parent_root,
state_root: Hash256::zero(),
let mut block = SignedBeaconBlock {
message: BeaconBlock {
slot: state.slot,
parent_root,
state_root: Hash256::zero(),
body: BeaconBlockBody {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings.into(),
attestations: self
.op_pool
.get_attestations(&state, &self.spec)
.map_err(BlockProductionError::OpPoolError)?
.into(),
deposits,
voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec).into(),
},
},
// The block is not signed here, that is the task of a validator client.
signature: Signature::empty_signature(),
body: BeaconBlockBody {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings.into(),
attestations: self
.op_pool
.get_attestations(&state, &self.spec)
.map_err(BlockProductionError::OpPoolError)?
.into(),
deposits,
voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec).into(),
},
};
per_block_processing(
@@ -1482,7 +1432,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let state_root = state.update_tree_hash_cache()?;
block.state_root = state_root;
block.message.state_root = state_root;
metrics::inc_counter(&metrics::BLOCK_PRODUCTION_SUCCESSES);
metrics::stop_timer(timer);
@@ -1490,12 +1440,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
trace!(
self.log,
"Produced beacon block";
"parent" => format!("{}", block.parent_root),
"attestations" => block.body.attestations.len(),
"slot" => block.slot
"parent" => format!("{}", block.message.parent_root),
"attestations" => block.message.body.attestations.len(),
"slot" => block.message.slot
);
Ok((block, state))
Ok((block.message, state))
}
/// Execute the fork choice algorithm and enthrone the result as the canonical head.
@@ -1516,13 +1466,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.get_block(&beacon_block_root)?
.ok_or_else(|| Error::MissingBeaconBlock(beacon_block_root))?;
let beacon_state_root = beacon_block.state_root;
let beacon_state_root = beacon_block.state_root();
let beacon_state: BeaconState<T::EthSpec> = self
.get_state(&beacon_state_root, Some(beacon_block.slot))?
.get_state(&beacon_state_root, Some(beacon_block.slot()))?
.ok_or_else(|| Error::MissingBeaconState(beacon_state_root))?;
let previous_slot = self.head_info()?.slot;
let new_slot = beacon_block.slot;
let new_slot = beacon_block.slot();
// Note: this will declare a re-org if we skip `SLOTS_PER_HISTORICAL_ROOT` blocks
// between calls to fork choice without swapping between chains. This seems like an
@@ -1541,7 +1491,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
"Beacon chain re-org";
"previous_head" => format!("{}", self.head_info()?.block_root),
"previous_slot" => previous_slot,
"new_head_parent" => format!("{}", beacon_block.parent_root),
"new_head_parent" => format!("{}", beacon_block.parent_root()),
"new_head" => format!("{}", beacon_block_root),
"new_slot" => new_slot
);
@@ -1636,7 +1586,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let finalized_block = self
.store
.get_block(&finalized_block_root)?
.ok_or_else(|| Error::MissingBeaconBlock(finalized_block_root))?;
.ok_or_else(|| Error::MissingBeaconBlock(finalized_block_root))?
.message;
let new_finalized_epoch = finalized_block.slot.epoch(T::EthSpec::slots_per_epoch());
@@ -1678,7 +1629,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn is_new_block_root(&self, beacon_block_root: &Hash256) -> Result<bool, Error> {
Ok(!self
.store
.exists::<BeaconBlock<T::EthSpec>>(beacon_block_root)?)
.exists::<SignedBeaconBlock<T::EthSpec>>(beacon_block_root)?)
}
/// Dumps the entire canonical chain, from the head to genesis to a vector for analysis.
@@ -1698,20 +1649,19 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
dump.push(last_slot.clone());
loop {
let beacon_block_root = last_slot.beacon_block.parent_root;
let beacon_block_root = last_slot.beacon_block.parent_root();
if beacon_block_root == Hash256::zero() {
break; // Genesis has been reached.
}
let beacon_block: BeaconBlock<T::EthSpec> =
self.store.get(&beacon_block_root)?.ok_or_else(|| {
Error::DBInconsistent(format!("Missing block {}", beacon_block_root))
})?;
let beacon_state_root = beacon_block.state_root;
let beacon_block = self.store.get_block(&beacon_block_root)?.ok_or_else(|| {
Error::DBInconsistent(format!("Missing block {}", beacon_block_root))
})?;
let beacon_state_root = beacon_block.state_root();
let beacon_state = self
.store
.get_state(&beacon_state_root, Some(beacon_block.slot))?
.get_state(&beacon_state_root, Some(beacon_block.slot()))?
.ok_or_else(|| {
Error::DBInconsistent(format!("Missing state {:?}", beacon_state_root))
})?;

View File

@@ -16,7 +16,9 @@ use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;
use store::Store;
use types::{BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, Slot};
use types::{
BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, Signature, SignedBeaconBlock, Slot,
};
/// An empty struct used to "witness" all the `BeaconChainTypes` traits. It has no user-facing
/// functionality and only exists to satisfy the type system.
@@ -205,14 +207,13 @@ where
.clone()
.ok_or_else(|| "genesis_state requires a store")?;
let mut beacon_block = genesis_block(&beacon_state, &self.spec);
let beacon_block = genesis_block(&mut beacon_state, &self.spec)?;
beacon_state
.build_all_caches(&self.spec)
.map_err(|e| format!("Failed to build genesis state caches: {:?}", e))?;
let beacon_state_root = beacon_state.canonical_root();
beacon_block.state_root = beacon_state_root;
let beacon_state_root = beacon_block.message.state_root;
let beacon_block_root = beacon_block.canonical_root();
self.genesis_block_root = Some(beacon_block_root);
@@ -303,7 +304,7 @@ where
.build_all_caches(&self.spec)
.map_err(|e| format!("Failed to build state caches: {:?}", e))?;
if canonical_head.beacon_block.state_root != canonical_head.beacon_state_root {
if canonical_head.beacon_block.state_root() != canonical_head.beacon_state_root {
return Err("beacon_block.state_root != beacon_state".to_string());
}
@@ -345,7 +346,7 @@ where
"Beacon chain initialized";
"head_state" => format!("{}", head.beacon_state_root),
"head_block" => format!("{}", head.beacon_block_root),
"head_slot" => format!("{}", head.beacon_block.slot),
"head_slot" => format!("{}", head.beacon_block.slot()),
);
Ok(beacon_chain)
@@ -382,7 +383,7 @@ where
.ok_or_else(|| "fork_choice_backend requires a genesis_block_root")?;
let backend = ProtoArrayForkChoice::new(
finalized_checkpoint.beacon_block.slot,
finalized_checkpoint.beacon_block.message.slot,
// Note: here we set the `justified_epoch` to be the same as the epoch of the
// finalized checkpoint. Whilst this finalized checkpoint may actually point to
// a _later_ justified checkpoint, that checkpoint won't yet exist in the fork
@@ -512,12 +513,20 @@ where
}
}
fn genesis_block<T: EthSpec>(genesis_state: &BeaconState<T>, spec: &ChainSpec) -> BeaconBlock<T> {
let mut genesis_block = BeaconBlock::empty(&spec);
genesis_block.state_root = genesis_state.canonical_root();
genesis_block
fn genesis_block<T: EthSpec>(
genesis_state: &mut BeaconState<T>,
spec: &ChainSpec,
) -> Result<SignedBeaconBlock<T>, String> {
let mut genesis_block = SignedBeaconBlock {
message: BeaconBlock::empty(&spec),
// Empty signature, which should NEVER be read. This isn't to-spec, but makes the genesis
// block consistent with every other block.
signature: Signature::empty_signature(),
};
genesis_block.message.state_root = genesis_state
.update_tree_hash_cache()
.map_err(|e| format!("Error hashing genesis state: {:?}", e))?;
Ok(genesis_block)
}
#[cfg(test)]
@@ -581,14 +590,14 @@ mod test {
"should have the correct genesis time"
);
assert_eq!(
block.state_root,
block.state_root(),
state.canonical_root(),
"block should have correct state root"
);
assert_eq!(
chain
.store
.get::<BeaconBlock<_>>(&Hash256::zero())
.get_block(&Hash256::zero())
.expect("should read db")
.expect("should find genesis block"),
block,

View File

@@ -1,12 +1,12 @@
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode};
use types::{BeaconBlock, BeaconState, EthSpec, Hash256};
use types::{BeaconState, EthSpec, Hash256, SignedBeaconBlock};
/// Represents some block and it's associated state. Generally, this will be used for tracking the
/// Represents some block and its associated state. Generally, this will be used for tracking the
/// head, justified head and finalized head.
#[derive(Clone, Serialize, PartialEq, Debug, Encode, Decode)]
pub struct CheckPoint<E: EthSpec> {
pub beacon_block: BeaconBlock<E>,
pub beacon_block: SignedBeaconBlock<E>,
pub beacon_block_root: Hash256,
pub beacon_state: BeaconState<E>,
pub beacon_state_root: Hash256,
@@ -15,7 +15,7 @@ pub struct CheckPoint<E: EthSpec> {
impl<E: EthSpec> CheckPoint<E> {
/// Create a new checkpoint.
pub fn new(
beacon_block: BeaconBlock<E>,
beacon_block: SignedBeaconBlock<E>,
beacon_block_root: Hash256,
beacon_state: BeaconState<E>,
beacon_state_root: Hash256,
@@ -31,7 +31,7 @@ impl<E: EthSpec> CheckPoint<E> {
/// Update all fields of the checkpoint.
pub fn update(
&mut self,
beacon_block: BeaconBlock<E>,
beacon_block: SignedBeaconBlock<E>,
beacon_block_root: Hash256,
beacon_state: BeaconState<E>,
beacon_state_root: Hash256,

View File

@@ -3,15 +3,12 @@ use eth1::{Config as Eth1Config, Eth1Block, Service as HttpService};
use eth2_hashing::hash;
use exit_future::Exit;
use futures::Future;
use integer_sqrt::IntegerSquareRoot;
use rand::prelude::*;
use slog::{crit, debug, error, trace, Logger};
use slog::{debug, error, trace, Logger};
use ssz_derive::{Decode, Encode};
use state_processing::per_block_processing::get_new_eth1_data;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::iter::DoubleEndedIterator;
use std::iter::FromIterator;
use std::marker::PhantomData;
use std::sync::Arc;
use store::{Error as StoreError, Store};
@@ -21,7 +18,6 @@ use types::{
};
type BlockNumber = u64;
type Eth1DataBlockNumber = HashMap<Eth1Data, BlockNumber>;
type Eth1DataVoteCount = HashMap<(Eth1Data, BlockNumber), u64>;
#[derive(Debug, PartialEq)]
@@ -280,12 +276,7 @@ impl<T: EthSpec, S: Store<T>> CachingEth1Backend<T, S> {
impl<T: EthSpec, S: Store<T>> Eth1ChainBackend<T, S> for CachingEth1Backend<T, S> {
fn eth1_data(&self, state: &BeaconState<T>, spec: &ChainSpec) -> Result<Eth1Data, Error> {
// Note: we do not return random junk if this function call fails as it would be caused by
// an internal error.
let prev_eth1_hash = eth1_block_hash_at_start_of_voting_period(self.store.clone(), state)?;
let period = T::SlotsPerEth1VotingPeriod::to_u64();
let eth1_follow_distance = spec.eth1_follow_distance;
let voting_period_start_slot = (state.slot / period) * period;
let voting_period_start_seconds = slot_start_seconds::<T>(
state.genesis_time,
@@ -295,82 +286,54 @@ impl<T: EthSpec, S: Store<T>> Eth1ChainBackend<T, S> for CachingEth1Backend<T, S
let blocks = self.core.blocks().read();
let (new_eth1_data, all_eth1_data) = if let Some(sets) = eth1_data_sets(
blocks.iter(),
prev_eth1_hash,
voting_period_start_seconds,
spec,
&self.log,
) {
sets
} else {
// The algorithm was unable to find the `new_eth1_data` and `all_eth1_data` sets.
//
// This is likely because the caches are empty or the previous eth1 block hash is not
// in the cache.
//
// This situation can also be caused when a testnet does not have an adequate delay
// between the eth1 genesis block and the eth2 genesis block. This delay needs to be at
// least `2 * ETH1_FOLLOW_DISTANCE`.
crit!(
self.log,
"Unable to find eth1 data sets";
"lowest_block_number" => self.core.lowest_block_number(),
"earliest_block_timestamp" => self.core.earliest_block_timestamp(),
"genesis_time" => state.genesis_time,
"outcome" => "casting random eth1 vote"
);
return Ok(random_eth1_data());
};
let votes_to_consider =
get_votes_to_consider(blocks.iter(), voting_period_start_seconds, spec);
trace!(
self.log,
"Found eth1 data sets";
"all_eth1_data" => all_eth1_data.len(),
"new_eth1_data" => new_eth1_data.len(),
"Found eth1 data votes_to_consider";
"votes_to_consider" => votes_to_consider.len(),
);
let valid_votes = collect_valid_votes(state, new_eth1_data, all_eth1_data);
let valid_votes = collect_valid_votes(state, &votes_to_consider);
let eth1_data = if let Some(eth1_data) = find_winning_vote(valid_votes) {
eth1_data
} else {
// In this case, there are no other viable votes (perhaps there are no votes yet or all
// the existing votes are junk).
// In this case, there are no valid votes available.
//
// Here we choose the latest block in our voting window.
blocks
// Here we choose the eth1_data corresponding to the latest block in our voting window.
// If no votes exist, choose `state.eth1_data` as default vote.
let default_vote = votes_to_consider
.iter()
.rev()
.skip_while(|eth1_block| eth1_block.timestamp > voting_period_start_seconds)
.nth(eth1_follow_distance as usize)
.map(|block| {
trace!(
.max_by(|(_, x), (_, y)| x.cmp(y))
.map(|vote| {
let vote = vote.0.clone();
debug!(
self.log,
"Choosing default eth1_data";
"eth1_block_number" => block.number,
"eth1_block_hash" => format!("{:?}", block.hash),
"No valid eth1_data votes";
"outcome" => "Casting vote corresponding to last candidate eth1 block",
);
block
vote
})
.and_then(|block| block.clone().eth1_data())
.unwrap_or_else(|| {
crit!(
let vote = state.eth1_data.clone();
error!(
self.log,
"Unable to find a winning eth1 vote";
"outcome" => "casting random eth1 vote"
"No valid eth1_data votes, `votes_to_consider` empty";
"lowest_block_number" => self.core.lowest_block_number(),
"earliest_block_timestamp" => self.core.earliest_block_timestamp(),
"genesis_time" => state.genesis_time,
"outcome" => "casting `state.eth1_data` as eth1 vote"
);
random_eth1_data()
})
metrics::inc_counter(&metrics::DEFAULT_ETH1_VOTES);
vote
});
default_vote
};
debug!(
self.log,
"Produced vote for eth1 chain";
"is_period_tail" => is_period_tail(state),
"deposit_root" => format!("{:?}", eth1_data.deposit_root),
"deposit_count" => eth1_data.deposit_count,
"block_hash" => format!("{:?}", eth1_data.block_hash),
@@ -432,139 +395,47 @@ impl<T: EthSpec, S: Store<T>> Eth1ChainBackend<T, S> for CachingEth1Backend<T, S
}
}
/// Produces an `Eth1Data` with all fields sourced from `rand::thread_rng()`.
fn random_eth1_data() -> Eth1Data {
let mut rng = rand::thread_rng();
metrics::inc_counter(&metrics::JUNK_ETH1_VOTES);
macro_rules! rand_bytes {
($num_bytes: expr) => {{
let mut arr = [0_u8; $num_bytes];
rng.fill(&mut arr[..]);
arr
}};
}
// Note: it seems easier to just use `Hash256::random(..)` to get the hash values, however I
// prefer to be explicit about the source of entropy instead of relying upon the maintainers of
// `Hash256` to ensure their entropy is suitable for our purposes.
Eth1Data {
block_hash: Hash256::from_slice(&rand_bytes!(32)),
deposit_root: Hash256::from_slice(&rand_bytes!(32)),
deposit_count: u64::from_le_bytes(rand_bytes!(8)),
}
}
/// Returns `state.eth1_data.block_hash` at the start of eth1 voting period defined by
/// `state.slot`.
fn eth1_block_hash_at_start_of_voting_period<T: EthSpec, S: Store<T>>(
store: Arc<S>,
state: &BeaconState<T>,
) -> Result<Hash256, Error> {
let period = T::SlotsPerEth1VotingPeriod::to_u64();
if !eth1_data_change_is_possible(state) {
// If there are less than 50% of the votes in the current state, it's impossible that the
// `eth1_data.block_hash` has changed from the value at `state.eth1_data.block_hash`.
Ok(state.eth1_data.block_hash)
} else {
// If there have been more than 50% of votes in this period it's possible (but not
// necessary) that the `state.eth1_data.block_hash` has been changed since the start of the
// voting period.
let slot = (state.slot / period) * period;
let prev_state_root = state
.get_state_root(slot)
.map_err(Error::UnableToGetPreviousStateRoot)?;
store
.get_state(&prev_state_root, Some(slot))
.map_err(Error::StoreError)?
.map(|state| state.eth1_data.block_hash)
.ok_or_else(|| Error::PreviousStateNotInDB(*prev_state_root))
}
}
/// Returns true if there are enough eth1 votes in the given `state` to have updated
/// `state.eth1_data`.
fn eth1_data_change_is_possible<E: EthSpec>(state: &BeaconState<E>) -> bool {
2 * state.eth1_data_votes.len() > E::SlotsPerEth1VotingPeriod::to_usize()
}
/// Calculates and returns `(new_eth1_data, all_eth1_data)` for the given `state`, based upon the
/// blocks in the `block` iterator.
/// Get all votes from eth1 blocks which are in the list of candidate blocks for the
/// current eth1 voting period.
///
/// `prev_eth1_hash` is the `eth1_data.block_hash` at the start of the voting period defined by
/// `state.slot`.
fn eth1_data_sets<'a, I>(
/// Returns a hashmap of `Eth1Data` to its associated eth1 `block_number`.
fn get_votes_to_consider<'a, I>(
blocks: I,
prev_eth1_hash: Hash256,
voting_period_start_seconds: u64,
spec: &ChainSpec,
log: &Logger,
) -> Option<(Eth1DataBlockNumber, Eth1DataBlockNumber)>
) -> HashMap<Eth1Data, u64>
where
I: DoubleEndedIterator<Item = &'a Eth1Block> + Clone,
{
let eth1_follow_distance = spec.eth1_follow_distance;
let in_scope_eth1_data = blocks
blocks
.rev()
.skip_while(|eth1_block| eth1_block.timestamp > voting_period_start_seconds)
.skip(eth1_follow_distance as usize)
.filter_map(|block| Some((block.clone().eth1_data()?, block.number)));
if in_scope_eth1_data
.clone()
.any(|(eth1_data, _)| eth1_data.block_hash == prev_eth1_hash)
{
let new_eth1_data = in_scope_eth1_data
.clone()
.take(eth1_follow_distance as usize);
let all_eth1_data =
in_scope_eth1_data.take_while(|(eth1_data, _)| eth1_data.block_hash != prev_eth1_hash);
Some((
HashMap::from_iter(new_eth1_data),
HashMap::from_iter(all_eth1_data),
))
} else {
error!(
log,
"The previous eth1 hash is not in cache";
"previous_hash" => format!("{:?}", prev_eth1_hash)
);
None
}
.skip_while(|eth1_block| !is_candidate_block(eth1_block, voting_period_start_seconds, spec))
.take_while(|eth1_block| is_candidate_block(eth1_block, voting_period_start_seconds, spec))
.filter_map(|eth1_block| {
eth1_block
.clone()
.eth1_data()
.map(|eth1_data| (eth1_data, eth1_block.number))
})
.collect()
}
/// Selects and counts the votes in `state.eth1_data_votes`, if they appear in `new_eth1_data` or
/// `all_eth1_data` when it is the voting period tail.
/// Collect all valid votes that are cast during the current voting period.
/// Return hashmap with count of each vote cast.
fn collect_valid_votes<T: EthSpec>(
state: &BeaconState<T>,
new_eth1_data: Eth1DataBlockNumber,
all_eth1_data: Eth1DataBlockNumber,
votes_to_consider: &HashMap<Eth1Data, BlockNumber>,
) -> Eth1DataVoteCount {
let mut valid_votes = HashMap::new();
state
.eth1_data_votes
.iter()
.filter_map(|vote| {
new_eth1_data
.get(vote)
.map(|block_number| (vote.clone(), *block_number))
.or_else(|| {
if is_period_tail(state) {
all_eth1_data
.get(vote)
.map(|block_number| (vote.clone(), *block_number))
} else {
None
}
})
if let Some(block_num) = votes_to_consider.get(vote) {
Some((vote.clone(), *block_num))
} else {
None
}
})
.for_each(|(eth1_data, block_number)| {
valid_votes
@@ -572,19 +443,9 @@ fn collect_valid_votes<T: EthSpec>(
.and_modify(|count| *count += 1)
.or_insert(1_u64);
});
valid_votes
}
/// Indicates if the given `state` is in the tail of it's eth1 voting period (i.e., in the later
/// slots).
fn is_period_tail<E: EthSpec>(state: &BeaconState<E>) -> bool {
let slots_per_eth1_voting_period = E::SlotsPerEth1VotingPeriod::to_u64();
let slot = state.slot % slots_per_eth1_voting_period;
slot >= slots_per_eth1_voting_period.integer_sqrt()
}
/// Selects the winning vote from `valid_votes`.
fn find_winning_vote(valid_votes: Eth1DataVoteCount) -> Option<Eth1Data> {
valid_votes
@@ -609,10 +470,24 @@ fn slot_start_seconds<T: EthSpec>(
genesis_unix_seconds + slot.as_u64() * milliseconds_per_slot / 1_000
}
/// Returns a boolean denoting if a given `Eth1Block` is a candidate for `Eth1Data` calculation
/// at the timestamp `period_start`.
///
/// Note: `period_start` needs to be atleast (`spec.seconds_per_eth1_block * spec.eth1_follow_distance * 2`)
/// for this function to return meaningful values.
fn is_candidate_block(block: &Eth1Block, period_start: u64, spec: &ChainSpec) -> bool {
block.timestamp
<= period_start.saturating_sub(spec.seconds_per_eth1_block * spec.eth1_follow_distance)
&& block.timestamp
>= period_start
.saturating_sub(spec.seconds_per_eth1_block * spec.eth1_follow_distance * 2)
}
#[cfg(test)]
mod test {
use super::*;
use environment::null_logger;
use std::iter::FromIterator;
use types::{test_utils::DepositTestTask, MinimalEthSpec};
type E = MinimalEthSpec;
@@ -625,9 +500,14 @@ mod test {
}
}
#[test]
fn random_eth1_data_doesnt_panic() {
random_eth1_data();
fn get_voting_period_start_seconds(state: &BeaconState<E>, spec: &ChainSpec) -> u64 {
let period = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let voting_period_start_slot = (state.slot / period) * period;
slot_start_seconds::<E>(
state.genesis_time,
spec.milliseconds_per_slot,
voting_period_start_slot,
)
}
#[test]
@@ -709,7 +589,7 @@ mod test {
assert!(
eth1_chain
.deposits_for_block_inclusion(&state, &random_eth1_data(), spec)
.deposits_for_block_inclusion(&state, &Eth1Data::default(), spec)
.is_ok(),
"should succeed if cache is empty but no deposits are required"
);
@@ -718,7 +598,7 @@ mod test {
assert!(
eth1_chain
.deposits_for_block_inclusion(&state, &random_eth1_data(), spec)
.deposits_for_block_inclusion(&state, &Eth1Data::default(), spec)
.is_err(),
"should fail to get deposits if required, but cache is empty"
);
@@ -762,7 +642,7 @@ mod test {
assert!(
eth1_chain
.deposits_for_block_inclusion(&state, &random_eth1_data(), spec)
.deposits_for_block_inclusion(&state, &Eth1Data::default(), spec)
.is_ok(),
"should succeed if no deposits are required"
);
@@ -774,7 +654,7 @@ mod test {
state.eth1_data.deposit_count = i as u64;
let deposits_for_inclusion = eth1_chain
.deposits_for_block_inclusion(&state, &random_eth1_data(), spec)
.deposits_for_block_inclusion(&state, &Eth1Data::default(), spec)
.unwrap_or_else(|_| panic!("should find deposit for {}", i));
let expected_len =
@@ -822,24 +702,20 @@ mod test {
let a = eth1_chain
.eth1_data_for_block_production(&state, &spec)
.expect("should produce first random eth1 data");
let b = eth1_chain
.eth1_data_for_block_production(&state, &spec)
.expect("should produce second random eth1 data");
assert!(
a != b,
"random votes should be returned with an empty cache"
.expect("should produce default eth1 data vote");
assert_eq!(
a, state.eth1_data,
"default vote should be same as state.eth1_data"
);
}
#[test]
fn eth1_data_unknown_previous_state() {
fn default_vote() {
let spec = &E::default_spec();
let period = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let slots_per_eth1_voting_period = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let eth1_follow_distance = spec.eth1_follow_distance;
let eth1_chain = get_eth1_chain();
let store = eth1_chain.backend.store.clone();
assert_eq!(
eth1_chain.use_dummy_backend, false,
@@ -847,120 +723,41 @@ mod test {
);
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), &spec);
let mut prev_state = state.clone();
prev_state.slot = Slot::new(period * 1_000);
state.slot = Slot::new(period * 1_000 + period / 2);
state.slot = Slot::from(slots_per_eth1_voting_period * 10);
let follow_distance_seconds = eth1_follow_distance * spec.seconds_per_eth1_block;
let voting_period_start = get_voting_period_start_seconds(&state, &spec);
let start_eth1_block = voting_period_start - follow_distance_seconds * 2;
let end_eth1_block = voting_period_start - follow_distance_seconds;
// Add 50% of the votes so a lookup is required.
for _ in 0..=period / 2 {
state
.eth1_data_votes
.push(random_eth1_data())
.expect("should push eth1 vote");
}
// Populate blocks cache with candidate eth1 blocks
let blocks = (start_eth1_block..end_eth1_block)
.map(|i| get_eth1_block(i, i))
.collect::<Vec<_>>();
(0..2048).for_each(|i| {
blocks.iter().for_each(|block| {
eth1_chain
.backend
.core
.blocks()
.write()
.insert_root_or_child(get_eth1_block(i, i))
.insert_root_or_child(block.clone())
.expect("should add blocks to cache");
});
let expected_root = Hash256::from_low_u64_be(u64::max_value());
prev_state.eth1_data.block_hash = expected_root;
assert!(
prev_state.eth1_data != state.eth1_data,
"test requires state eth1_data are different"
);
store
.put_state(
&state
.get_state_root(prev_state.slot)
.expect("should find state root"),
prev_state,
)
.expect("should store state");
let a = eth1_chain
let vote = eth1_chain
.eth1_data_for_block_production(&state, &spec)
.expect("should produce first random eth1 data");
let b = eth1_chain
.eth1_data_for_block_production(&state, &spec)
.expect("should produce second random eth1 data");
assert!(
a != b,
"random votes should be returned if the previous eth1 data block hash is unknown"
);
}
}
mod prev_block_hash {
use super::*;
use store::MemoryStore;
#[test]
fn without_store_lookup() {
let spec = &E::default_spec();
let store = Arc::new(MemoryStore::open());
let state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
.expect("should produce default eth1 data vote");
assert_eq!(
eth1_block_hash_at_start_of_voting_period(store, &state),
Ok(state.eth1_data.block_hash),
"should return the states eth1 data in the first half of the period"
);
}
#[test]
fn with_store_lookup() {
let spec = &E::default_spec();
let store = Arc::new(MemoryStore::open());
let period = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
let mut prev_state = state.clone();
state.slot = Slot::new(period / 2);
// Add 50% of the votes so a lookup is required.
for _ in 0..=period / 2 {
state
.eth1_data_votes
.push(random_eth1_data())
.expect("should push eth1 vote");
}
let expected_root = Hash256::from_low_u64_be(42);
prev_state.eth1_data.block_hash = expected_root;
assert!(
prev_state.eth1_data != state.eth1_data,
"test requires state eth1_data are different"
);
store
.put_state(
&state
.get_state_root(Slot::new(0))
.expect("should find state root"),
prev_state,
)
.expect("should store state");
assert_eq!(
eth1_block_hash_at_start_of_voting_period(store, &state),
Ok(expected_root),
"should return the eth1_data from the previous state"
vote,
blocks
.last()
.expect("should have blocks")
.clone()
.eth1_data()
.expect("should have valid eth1 data"),
"default vote must correspond to last block in candidate blocks"
);
}
}
@@ -968,132 +765,58 @@ mod test {
mod eth1_data_sets {
use super::*;
fn get_voting_period_start_seconds(state: &BeaconState<E>, spec: &ChainSpec) -> u64 {
let period = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let voting_period_start_slot = (state.slot / period) * period;
slot_start_seconds::<E>(
state.genesis_time,
spec.milliseconds_per_slot,
voting_period_start_slot,
)
}
#[test]
fn empty_cache() {
let log = null_logger().unwrap();
let spec = &E::default_spec();
let state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
let prev_eth1_hash = Hash256::zero();
let blocks = vec![];
assert_eq!(
eth1_data_sets(
get_votes_to_consider(
blocks.iter(),
prev_eth1_hash,
get_voting_period_start_seconds(&state, spec),
&spec,
&log
),
None
);
}
#[test]
fn no_known_block_hash() {
let log = null_logger().unwrap();
let mut spec = E::default_spec();
spec.milliseconds_per_slot = 1_000;
let state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), &spec);
let prev_eth1_hash = Hash256::from_low_u64_be(42);
let blocks = vec![get_eth1_block(0, 0)];
assert_eq!(
eth1_data_sets(
blocks.iter(),
prev_eth1_hash,
get_voting_period_start_seconds(&state, &spec),
&spec,
&log
),
None
HashMap::new()
);
}
#[test]
fn ideal_scenario() {
let log = null_logger().unwrap();
let mut spec = E::default_spec();
spec.milliseconds_per_slot = 1_000;
let spec = E::default_spec();
let slots_per_eth1_voting_period = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let eth1_follow_distance = spec.eth1_follow_distance;
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), &spec);
state.genesis_time = 0;
state.slot = Slot::from(slots_per_eth1_voting_period * 3);
state.slot = Slot::from(slots_per_eth1_voting_period * 10);
let prev_eth1_hash = Hash256::zero();
let blocks = (0..eth1_follow_distance * 4)
let follow_distance_seconds = eth1_follow_distance * spec.seconds_per_eth1_block;
let voting_period_start = get_voting_period_start_seconds(&state, &spec);
let start_eth1_block = voting_period_start - follow_distance_seconds * 2;
let end_eth1_block = voting_period_start - follow_distance_seconds;
let blocks = (start_eth1_block..end_eth1_block)
.map(|i| get_eth1_block(i, i))
.collect::<Vec<_>>();
let (new_eth1_data, all_eth1_data) = eth1_data_sets(
blocks.iter(),
prev_eth1_hash,
get_voting_period_start_seconds(&state, &spec),
&spec,
&log,
)
.expect("should find data");
let votes_to_consider =
get_votes_to_consider(blocks.iter(), voting_period_start, &spec);
assert_eq!(
all_eth1_data.len(),
eth1_follow_distance as usize * 2,
"all_eth1_data should have appropriate length"
);
assert_eq!(
new_eth1_data.len(),
eth1_follow_distance as usize,
"new_eth1_data should have appropriate length"
votes_to_consider.len() as u64,
end_eth1_block - start_eth1_block,
"all produced eth1 blocks should be in votes to consider"
);
for (eth1_data, block_number) in &new_eth1_data {
assert_eq!(
all_eth1_data.get(eth1_data),
Some(block_number),
"all_eth1_data should contain all items in new_eth1_data"
);
}
(1..=eth1_follow_distance * 2)
(start_eth1_block..end_eth1_block)
.map(|i| get_eth1_block(i, i))
.for_each(|eth1_block| {
assert_eq!(
eth1_block.number,
*all_eth1_data
*votes_to_consider
.get(&eth1_block.clone().eth1_data().unwrap())
.expect("all_eth1_data should have expected block")
)
});
(eth1_follow_distance + 1..=eth1_follow_distance * 2)
.map(|i| get_eth1_block(i, i))
.for_each(|eth1_block| {
assert_eq!(
eth1_block.number,
*new_eth1_data
.get(&eth1_block.clone().eth1_data().unwrap())
.unwrap_or_else(|| panic!(
"new_eth1_data should have expected block #{}",
eth1_block.number
))
.expect("votes_to_consider should have expected block numbers")
)
});
}
@@ -1130,13 +853,11 @@ mod test {
let spec = &E::default_spec();
let state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
let all_eth1_data = get_eth1_data_vec(slots, 0);
let new_eth1_data = all_eth1_data[slots as usize / 2..].to_vec();
let votes_to_consider = get_eth1_data_vec(slots, 0);
let votes = collect_valid_votes(
&state,
HashMap::from_iter(new_eth1_data.into_iter()),
HashMap::from_iter(all_eth1_data.into_iter()),
&HashMap::from_iter(votes_to_consider.clone().into_iter()),
);
assert_eq!(
votes.len(),
@@ -1151,10 +872,9 @@ mod test {
let spec = &E::default_spec();
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
let all_eth1_data = get_eth1_data_vec(slots, 0);
let new_eth1_data = all_eth1_data[slots as usize / 2..].to_vec();
let votes_to_consider = get_eth1_data_vec(slots, 0);
state.eth1_data_votes = new_eth1_data[0..slots as usize / 4]
state.eth1_data_votes = votes_to_consider[0..slots as usize / 4]
.iter()
.map(|(eth1_data, _)| eth1_data)
.cloned()
@@ -1163,12 +883,11 @@ mod test {
let votes = collect_valid_votes(
&state,
HashMap::from_iter(new_eth1_data.clone().into_iter()),
HashMap::from_iter(all_eth1_data.into_iter()),
&HashMap::from_iter(votes_to_consider.clone().into_iter()),
);
assert_votes!(
votes,
new_eth1_data[0..slots as usize / 4].to_vec(),
votes_to_consider[0..slots as usize / 4].to_vec(),
"should find as many votes as were in the state"
);
}
@@ -1179,10 +898,9 @@ mod test {
let spec = &E::default_spec();
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
let all_eth1_data = get_eth1_data_vec(slots, 0);
let new_eth1_data = all_eth1_data[slots as usize / 2..].to_vec();
let votes_to_consider = get_eth1_data_vec(slots, 0);
let duplicate_eth1_data = new_eth1_data
let duplicate_eth1_data = votes_to_consider
.last()
.expect("should have some eth1 data")
.clone();
@@ -1196,8 +914,7 @@ mod test {
let votes = collect_valid_votes(
&state,
HashMap::from_iter(new_eth1_data.into_iter()),
HashMap::from_iter(all_eth1_data.into_iter()),
&HashMap::from_iter(votes_to_consider.clone().into_iter()),
);
assert_votes!(
votes,
@@ -1214,70 +931,6 @@ mod test {
"should have four votes"
);
}
#[test]
fn non_period_tail() {
let slots = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let spec = &E::default_spec();
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
state.slot = Slot::from(<E as EthSpec>::SlotsPerEpoch::to_u64()) * 10;
let all_eth1_data = get_eth1_data_vec(slots, 0);
let new_eth1_data = all_eth1_data[slots as usize / 2..].to_vec();
let non_new_eth1_data = all_eth1_data
.first()
.expect("should have some eth1 data")
.clone();
state.eth1_data_votes = vec![non_new_eth1_data.0].into();
let votes = collect_valid_votes(
&state,
HashMap::from_iter(new_eth1_data.into_iter()),
HashMap::from_iter(all_eth1_data.into_iter()),
);
assert_votes!(
votes,
vec![],
"should not find votes from all_eth1_data when it is not the period tail"
);
}
#[test]
fn period_tail() {
let slots_per_eth1_voting_period = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let slots = <E as EthSpec>::SlotsPerEth1VotingPeriod::to_u64();
let spec = &E::default_spec();
let mut state: BeaconState<E> = BeaconState::new(0, get_eth1_data(0), spec);
state.slot = Slot::from(<E as EthSpec>::SlotsPerEpoch::to_u64()) * 10
+ slots_per_eth1_voting_period.integer_sqrt();
let all_eth1_data = get_eth1_data_vec(slots, 0);
let new_eth1_data = all_eth1_data[slots as usize / 2..].to_vec();
let non_new_eth1_data = all_eth1_data
.first()
.expect("should have some eth1 data")
.clone();
state.eth1_data_votes = vec![non_new_eth1_data.0.clone()].into();
let votes = collect_valid_votes(
&state,
HashMap::from_iter(new_eth1_data.into_iter()),
HashMap::from_iter(all_eth1_data.into_iter()),
);
assert_votes!(
votes,
vec![non_new_eth1_data],
"should find all_eth1_data votes when it is the period tail"
);
}
}
mod winning_vote {

View File

@@ -1,6 +1,6 @@
use serde_derive::{Deserialize, Serialize};
use std::marker::PhantomData;
use types::{Attestation, BeaconBlock, Epoch, EthSpec, Hash256};
use types::{Attestation, Epoch, EthSpec, Hash256, SignedBeaconBlock};
pub use websocket_server::WebSocketSender;
pub trait EventHandler<T: EthSpec>: Sized + Send + Sync {
@@ -49,11 +49,11 @@ pub enum EventKind<T: EthSpec> {
},
BeaconBlockImported {
block_root: Hash256,
block: Box<BeaconBlock<T>>,
block: Box<SignedBeaconBlock<T>>,
},
BeaconBlockRejected {
reason: String,
block: Box<BeaconBlock<T>>,
block: Box<SignedBeaconBlock<T>>,
},
BeaconAttestationImported {
attestation: Box<Attestation<T>>,

View File

@@ -306,8 +306,11 @@ impl CheckpointManager {
.ok_or_else(|| Error::UnknownJustifiedBlock(block_root))?;
let state = chain
.get_state_caching_only_with_committee_caches(&block.state_root, Some(block.slot))?
.ok_or_else(|| Error::UnknownJustifiedState(block.state_root))?;
.get_state_caching_only_with_committee_caches(
&block.state_root(),
Some(block.slot()),
)?
.ok_or_else(|| Error::UnknownJustifiedState(block.state_root()))?;
Ok(get_effective_balances(&state))
}

View File

@@ -152,8 +152,8 @@ lazy_static! {
/*
* Eth1
*/
pub static ref JUNK_ETH1_VOTES: Result<IntCounter> =
try_create_int_counter("beacon_eth1_junk_votes", "Count of times we have voted junk for eth1 dat");
pub static ref DEFAULT_ETH1_VOTES: Result<IntCounter> =
try_create_int_counter("beacon_eth1_default_votes", "Count of times we have voted default value for eth1 data");
/*
* Chain Head

View File

@@ -16,10 +16,9 @@ use store::{
migrate::{BlockingMigrator, NullMigrator},
DiskStore, MemoryStore, Migrate, Store,
};
use tree_hash::{SignedRoot, TreeHash};
use types::{
AggregateSignature, Attestation, BeaconBlock, BeaconState, BitList, ChainSpec, Domain, EthSpec,
Hash256, Keypair, SecretKey, Signature, Slot,
AggregateSignature, Attestation, BeaconState, BitList, ChainSpec, Domain, EthSpec, Hash256,
Keypair, SecretKey, Signature, SignedBeaconBlock, SignedRoot, Slot,
};
pub use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY};
@@ -279,7 +278,7 @@ where
mut state: BeaconState<E>,
slot: Slot,
block_strategy: BlockStrategy,
) -> (BeaconBlock<E>, BeaconState<E>) {
) -> (SignedBeaconBlock<E>, BeaconState<E>) {
if slot < state.slot {
panic!("produce slot cannot be prior to the state slot");
}
@@ -308,24 +307,19 @@ where
let randao_reveal = {
let epoch = slot.epoch(E::slots_per_epoch());
let message = epoch.tree_hash_root();
let domain = self.spec.get_domain(epoch, Domain::Randao, fork);
Signature::new(&message, domain, sk)
let message = epoch.signing_root(domain);
Signature::new(message.as_bytes(), sk)
};
let (mut block, state) = self
let (block, state) = self
.chain
.produce_block_on_state(state, slot, randao_reveal)
.expect("should produce block");
block.signature = {
let message = block.signed_root();
let epoch = block.slot.epoch(E::slots_per_epoch());
let domain = self.spec.get_domain(epoch, Domain::BeaconProposer, fork);
Signature::new(&message, domain, sk)
};
let signed_block = block.sign(sk, &state.fork, &self.spec);
(block, state)
(signed_block, state)
}
/// Adds attestations to the `BeaconChain` operations pool and fork choice.
@@ -407,18 +401,17 @@ where
.expect("should be able to set aggregation bits");
let signature = {
let message = data.tree_hash_root();
let domain = spec.get_domain(
data.target.epoch,
Domain::BeaconAttester,
fork,
);
let message = data.signing_root(domain);
let mut agg_sig = AggregateSignature::new();
agg_sig.add(&Signature::new(
&message,
domain,
message.as_bytes(),
self.get_sk(*validator_index),
));
@@ -464,7 +457,7 @@ where
.head()
.expect("should get head")
.beacon_block
.slot;
.slot();
// Move to the next slot so we may produce some more blocks on the head.
self.advance_slot();