Merge pull request #129 from sigp/chain-update

Update `BeaconChain` struct
This commit is contained in:
Paul Hauner 2019-01-09 14:27:35 +11:00 committed by GitHub
commit de3ea2a64b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 876 additions and 556 deletions

View File

@ -34,7 +34,6 @@ name = "lighthouse"
[workspace]
members = [
"beacon_chain/attestation_validation",
"beacon_chain/chain",
"beacon_chain/genesis",
"beacon_chain/naive_fork_choice",
"beacon_chain/spec",
@ -43,10 +42,11 @@ members = [
"beacon_chain/utils/boolean-bitfield",
"beacon_chain/utils/hashing",
"beacon_chain/utils/honey-badger-split",
"beacon_chain/utils/slot-clock",
"beacon_chain/utils/slot_clock",
"beacon_chain/utils/ssz",
"beacon_chain/utils/vec_shuffle",
"beacon_chain/validator_induction",
"beacon_chain/validator_shuffling",
"lighthouse/beacon_chain",
"lighthouse/db",
]

View File

@ -1,16 +0,0 @@
[package]
name = "chain"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls = { path = "../utils/bls" }
db = { path = "../../lighthouse/db" }
genesis = { path = "../genesis" }
naive_fork_choice = { path = "../naive_fork_choice" }
spec = { path = "../spec" }
ssz = { path = "../utils/ssz" }
types = { path = "../types" }
validator_induction = { path = "../validator_induction" }
validator_shuffling = { path = "../validator_shuffling" }

View File

@ -1,29 +0,0 @@
use super::BeaconChain;
use db::ClientDB;
use types::Hash256;
pub enum BlockProcessingOutcome {
BlockAlreadyKnown,
NewCanonicalBlock,
NewReorgBlock,
NewForkBlock,
}
pub enum Error {
NotImplemented,
}
impl<T> BeaconChain<T>
where
T: ClientDB + Sized,
{
pub fn process_block(
&mut self,
_ssz: &[u8],
_present_slot: u64,
) -> Result<(BlockProcessingOutcome, Hash256), Error> {
// TODO: block processing has been removed.
// https://github.com/sigp/lighthouse/issues/98
Err(Error::NotImplemented)
}
}

View File

@ -1,110 +0,0 @@
extern crate db;
extern crate naive_fork_choice;
extern crate genesis;
extern crate spec;
extern crate ssz;
extern crate types;
extern crate validator_induction;
extern crate validator_shuffling;
mod block_processing;
mod maps;
mod stores;
use db::ClientDB;
use crate::maps::{generate_attester_and_proposer_maps, AttesterAndProposerMapError};
use crate::stores::BeaconChainStore;
use genesis::{genesis_beacon_state, GenesisError};
use spec::ChainSpec;
use std::collections::HashMap;
use std::sync::Arc;
use types::{AttesterMap, BeaconState, Hash256, ProposerMap};
#[derive(Debug, PartialEq)]
pub enum BeaconChainError {
InvalidGenesis,
InsufficientValidators,
UnableToGenerateMaps(AttesterAndProposerMapError),
GenesisError(GenesisError),
DBError(String),
}
pub struct BeaconChain<T: ClientDB + Sized> {
/// The last slot which has been finalized, this is common to all forks.
pub last_finalized_slot: u64,
/// A vec of all block heads (tips of chains).
pub head_block_hashes: Vec<Hash256>,
/// The index of the canonical block in `head_block_hashes`.
pub canonical_head_block_hash: usize,
/// An in-memory map of root hash to beacon state.
pub beacon_states: HashMap<Hash256, BeaconState>,
/// A map of crystallized state to a proposer and attester map.
pub attester_proposer_maps: HashMap<Hash256, (Arc<AttesterMap>, Arc<ProposerMap>)>,
/// A collection of database stores used by the chain.
pub store: BeaconChainStore<T>,
/// The chain configuration.
pub spec: ChainSpec,
}
impl<T> BeaconChain<T>
where
T: ClientDB + Sized,
{
pub fn new(store: BeaconChainStore<T>, spec: ChainSpec) -> Result<Self, BeaconChainError> {
if spec.initial_validators.is_empty() {
return Err(BeaconChainError::InsufficientValidators);
}
/*
* Generate and process the genesis state.
*/
let genesis_state = genesis_beacon_state(&spec)?;
let mut beacon_states = HashMap::new();
beacon_states.insert(genesis_state.canonical_root(), genesis_state.clone());
// TODO: implement genesis block
// https://github.com/sigp/lighthouse/issues/105
let canonical_latest_block_hash = Hash256::zero();
let head_block_hashes = vec![canonical_latest_block_hash];
let canonical_head_block_hash = 0;
let mut attester_proposer_maps = HashMap::new();
let (attester_map, proposer_map) = generate_attester_and_proposer_maps(
&genesis_state.shard_committees_at_slots,
0,
)?;
attester_proposer_maps.insert(
canonical_latest_block_hash,
(Arc::new(attester_map), Arc::new(proposer_map)),
);
Ok(Self {
last_finalized_slot: 0,
head_block_hashes,
canonical_head_block_hash,
beacon_states,
attester_proposer_maps,
store,
spec,
})
}
pub fn canonical_block_hash(&self) -> Hash256 {
self.head_block_hashes[self.canonical_head_block_hash]
}
}
impl From<AttesterAndProposerMapError> for BeaconChainError {
fn from(e: AttesterAndProposerMapError) -> BeaconChainError {
BeaconChainError::UnableToGenerateMaps(e)
}
}
impl From<GenesisError> for BeaconChainError {
fn from(e: GenesisError) -> BeaconChainError {
BeaconChainError::GenesisError(e)
}
}

View File

@ -1,127 +0,0 @@
use types::{AttesterMap, ProposerMap, ShardCommittee};
#[derive(Debug, PartialEq)]
pub enum AttesterAndProposerMapError {
NoShardCommitteeForSlot,
NoAvailableProposer,
}
/// Generate a map of `(slot, shard) |--> committee`.
///
/// The attester map is used to optimise the lookup of a committee.
pub fn generate_attester_and_proposer_maps(
shard_and_committee_for_slots: &Vec<Vec<ShardCommittee>>,
start_slot: u64,
) -> Result<(AttesterMap, ProposerMap), AttesterAndProposerMapError> {
let mut attester_map = AttesterMap::new();
let mut proposer_map = ProposerMap::new();
for (i, slot) in shard_and_committee_for_slots.iter().enumerate() {
/*
* Store the proposer for the block.
*/
let slot_number = (i as u64).saturating_add(start_slot);
let first_committee = &slot
.get(0)
.ok_or(AttesterAndProposerMapError::NoShardCommitteeForSlot)?
.committee;
let proposer_index = (slot_number as usize)
.checked_rem(first_committee.len())
.ok_or(AttesterAndProposerMapError::NoAvailableProposer)?;
proposer_map.insert(slot_number, first_committee[proposer_index]);
/*
* Loop through the shards and extend the attester map.
*/
for shard_and_committee in slot {
let committee = shard_and_committee.committee.clone();
attester_map.insert((slot_number, shard_and_committee.shard), committee);
}
}
Ok((attester_map, proposer_map))
}
#[cfg(test)]
mod tests {
use super::*;
fn sac_generator(
shard_count: u64,
slot_count: usize,
sac_per_slot: usize,
committee_size: usize,
) -> Vec<Vec<ShardCommittee>> {
let mut shard = 0;
let mut validator = 0;
let mut cycle = vec![];
for _ in 0..slot_count {
let mut slot: Vec<ShardCommittee> = vec![];
for _ in 0..sac_per_slot {
let mut sac = ShardCommittee {
shard: shard % shard_count,
committee: vec![],
};
for _ in 0..committee_size {
sac.committee.push(validator);
validator += 1;
}
slot.push(sac);
shard += 1;
}
cycle.push(slot);
}
cycle
}
#[test]
fn test_attester_proposer_maps_empty_slots() {
let sac = sac_generator(4, 4, 0, 1);
let result = generate_attester_and_proposer_maps(&sac, 0);
assert_eq!(
result,
Err(AttesterAndProposerMapError::NoShardCommitteeForSlot)
);
}
#[test]
fn test_attester_proposer_maps_empty_committees() {
let sac = sac_generator(4, 4, 1, 0);
let result = generate_attester_and_proposer_maps(&sac, 0);
assert_eq!(
result,
Err(AttesterAndProposerMapError::NoAvailableProposer)
);
}
#[test]
fn test_attester_proposer_maps_scenario_a() {
let sac = sac_generator(4, 4, 1, 1);
let (a, p) = generate_attester_and_proposer_maps(&sac, 0).unwrap();
assert_eq!(*p.get(&0).unwrap(), 0);
assert_eq!(*p.get(&1).unwrap(), 1);
assert_eq!(*p.get(&2).unwrap(), 2);
assert_eq!(*p.get(&3).unwrap(), 3);
assert_eq!(*a.get(&(0, 0)).unwrap(), vec![0]);
assert_eq!(*a.get(&(1, 1)).unwrap(), vec![1]);
assert_eq!(*a.get(&(2, 2)).unwrap(), vec![2]);
assert_eq!(*a.get(&(3, 3)).unwrap(), vec![3]);
}
#[test]
fn test_attester_proposer_maps_scenario_b() {
let sac = sac_generator(4, 4, 1, 4);
let (a, p) = generate_attester_and_proposer_maps(&sac, 0).unwrap();
assert_eq!(*p.get(&0).unwrap(), 0);
assert_eq!(*p.get(&1).unwrap(), 5);
assert_eq!(*p.get(&2).unwrap(), 10);
assert_eq!(*p.get(&3).unwrap(), 15);
assert_eq!(*a.get(&(0, 0)).unwrap(), vec![0, 1, 2, 3]);
assert_eq!(*a.get(&(1, 1)).unwrap(), vec![4, 5, 6, 7]);
assert_eq!(*a.get(&(2, 2)).unwrap(), vec![8, 9, 10, 11]);
assert_eq!(*a.get(&(3, 3)).unwrap(), vec![12, 13, 14, 15]);
}
}

View File

@ -1,9 +0,0 @@
use db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore};
use db::ClientDB;
use std::sync::Arc;
pub struct BeaconChainStore<T: ClientDB + Sized> {
pub block: Arc<BeaconBlockStore<T>>,
pub pow_chain: Arc<PoWChainStore<T>>,
pub validator: Arc<ValidatorStore<T>>,
}

View File

@ -1,14 +1,14 @@
use bls::{Signature, BLS_AGG_SIG_BYTE_SIZE};
use spec::ChainSpec;
use ssz::{encode::encode_length, Decodable, LENGTH_BYTES};
use types::{BeaconBlock, BeaconBlockBody};
use types::{BeaconBlock, BeaconBlockBody, Hash256};
/// Generate a genesis BeaconBlock.
pub fn genesis_beacon_block(spec: &ChainSpec) -> BeaconBlock {
pub fn genesis_beacon_block(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock {
BeaconBlock {
slot: spec.initial_slot_number,
parent_root: spec.zero_hash,
state_root: spec.zero_hash,
state_root,
randao_reveal: spec.zero_hash,
candidate_pow_receipt_root: spec.zero_hash,
signature: genesis_signature(),
@ -42,8 +42,9 @@ mod tests {
#[test]
fn test_genesis() {
let spec = ChainSpec::foundation();
let state_root = Hash256::from("cats".as_bytes());
// This only checks that the function runs without panic.
genesis_beacon_block(&spec);
genesis_beacon_block(state_root, &spec);
}
}

View File

@ -15,8 +15,8 @@ pub enum ForkChoiceError {
}
pub fn naive_fork_choice<T>(
head_block_hashes: &Vec<Hash256>,
block_store: Arc<BeaconBlockStore<T>>,
head_block_hashes: &[Hash256],
block_store: &Arc<BeaconBlockStore<T>>,
) -> Result<Option<usize>, ForkChoiceError>
where
T: ClientDB + Sized,
@ -28,7 +28,7 @@ where
*/
for (index, block_hash) in head_block_hashes.iter().enumerate() {
let ssz = block_store
.get_serialized_block(&block_hash.to_vec()[..])?
.get(&block_hash)?
.ok_or(ForkChoiceError::MissingBlock)?;
let (block, _) = BeaconBlock::ssz_decode(&ssz, 0)?;
head_blocks.push((index, block));

View File

@ -68,7 +68,7 @@ impl ChainSpec {
*/
initial_validators: initial_validators_for_testing(),
initial_balances: initial_balances_for_testing(),
genesis_time: 1544672897,
genesis_time: 1_544_672_897,
processed_pow_receipt_root: Hash256::from("pow_root".as_bytes()),
}
}
@ -108,7 +108,7 @@ fn initial_validators_for_testing() -> Vec<ValidatorRecord> {
exit_count: 0,
custody_commitment: Hash256::zero(),
latest_custody_reseed_slot: 0,
penultimate_custody_reseed_slot: 0
penultimate_custody_reseed_slot: 0,
};
initial_validators.push(validator_record);
}

View File

@ -8,5 +8,6 @@ edition = "2018"
bls = { path = "../utils/bls" }
boolean-bitfield = { path = "../utils/boolean-bitfield" }
ethereum-types = "0.4.0"
hashing = { path = "../utils/hashing" }
rand = "0.5.5"
ssz = { path = "../utils/ssz" }

View File

@ -64,8 +64,7 @@ impl<T: RngCore> TestRandom<T> for Attestation {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -104,8 +104,7 @@ impl<T: RngCore> TestRandom<T> for AttestationData {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -1,7 +1,8 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream};
use super::{BeaconBlockBody, Hash256};
use crate::test_utils::TestRandom;
use bls::Signature;
use hashing::canonical_hash;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone)]
@ -15,6 +16,14 @@ pub struct BeaconBlock {
pub body: BeaconBlockBody,
}
impl BeaconBlock {
pub fn canonical_root(&self) -> Hash256 {
// TODO: implement tree hashing.
// https://github.com/sigp/lighthouse/issues/70
Hash256::from(&canonical_hash(&ssz_encode(self))[..])
}
}
impl Encodable for BeaconBlock {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slot);
@ -70,8 +79,7 @@ impl<T: RngCore> TestRandom<T> for BeaconBlock {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -59,8 +59,7 @@ impl<T: RngCore> TestRandom<T> for BeaconBlockBody {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -7,8 +7,9 @@ use super::shard_reassignment_record::ShardReassignmentRecord;
use super::validator_record::ValidatorRecord;
use super::Hash256;
use crate::test_utils::TestRandom;
use hashing::canonical_hash;
use rand::RngCore;
use ssz::{Decodable, DecodeError, Encodable, SszStream};
use ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream};
#[derive(Debug, PartialEq, Clone, Default)]
pub struct BeaconState {
@ -52,7 +53,7 @@ impl BeaconState {
pub fn canonical_root(&self) -> Hash256 {
// TODO: implement tree hashing.
// https://github.com/sigp/lighthouse/issues/70
Hash256::zero()
Hash256::from(&canonical_hash(&ssz_encode(self))[..])
}
}
@ -170,3 +171,21 @@ impl<T: RngCore> TestRandom<T> for BeaconState {
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = BeaconState::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -44,8 +44,7 @@ impl<T: RngCore> TestRandom<T> for CandidatePoWReceiptRootRecord {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -44,8 +44,7 @@ impl<T: RngCore> TestRandom<T> for CasperSlashing {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -54,8 +54,7 @@ impl<T: RngCore> TestRandom<T> for CrosslinkRecord {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -49,8 +49,7 @@ impl<T: RngCore> TestRandom<T> for DepositData {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -60,8 +60,7 @@ impl<T: RngCore> TestRandom<T> for DepositInput {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -49,8 +49,7 @@ impl<T: RngCore> TestRandom<T> for Exit {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -48,8 +48,7 @@ impl<T: RngCore> TestRandom<T> for ForkData {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -5,8 +5,8 @@ extern crate ssz;
pub mod test_utils;
pub mod attestation_data;
pub mod attestation;
pub mod attestation_data;
pub mod beacon_block;
pub mod beacon_block_body;
pub mod beacon_state;
@ -23,15 +23,17 @@ pub mod proposal_signed_data;
pub mod proposer_slashing;
pub mod shard_committee;
pub mod shard_reassignment_record;
pub mod special_record;
pub mod slashable_vote_data;
pub mod special_record;
pub mod validator_record;
pub mod readers;
use self::ethereum_types::{H160, H256, U256};
use std::collections::HashMap;
pub use crate::attestation_data::AttestationData;
pub use crate::attestation::Attestation;
pub use crate::attestation_data::AttestationData;
pub use crate::beacon_block::BeaconBlock;
pub use crate::beacon_block_body::BeaconBlockBody;
pub use crate::beacon_state::BeaconState;
@ -45,8 +47,8 @@ pub use crate::fork_data::ForkData;
pub use crate::pending_attestation_record::PendingAttestationRecord;
pub use crate::proposal_signed_data::ProposalSignedData;
pub use crate::proposer_slashing::ProposerSlashing;
pub use crate::slashable_vote_data::SlashableVoteData;
pub use crate::shard_committee::ShardCommittee;
pub use crate::slashable_vote_data::SlashableVoteData;
pub use crate::special_record::{SpecialRecord, SpecialRecordKind};
pub use crate::validator_record::{ValidatorRecord, ValidatorStatus};

View File

@ -54,8 +54,7 @@ impl<T: RngCore> TestRandom<T> for PendingAttestationRecord {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -49,8 +49,7 @@ impl<T: RngCore> TestRandom<T> for ProposalSignedData {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -60,8 +60,7 @@ impl<T: RngCore> TestRandom<T> for ProposerSlashing {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -0,0 +1,40 @@
use crate::{BeaconBlock, Hash256};
use std::fmt::Debug;
/// The `BeaconBlockReader` provides interfaces for reading a subset of fields of a `BeaconBlock`.
///
/// The purpose of this trait is to allow reading from either;
/// - a standard `BeaconBlock` struct, or
/// - a SSZ serialized byte array.
///
/// Note: presently, direct SSZ reading has not been implemented so this trait is being used for
/// "future proofing".
pub trait BeaconBlockReader: Debug + PartialEq {
fn slot(&self) -> u64;
fn parent_root(&self) -> Hash256;
fn state_root(&self) -> Hash256;
fn canonical_root(&self) -> Hash256;
fn into_beacon_block(self) -> Option<BeaconBlock>;
}
impl BeaconBlockReader for BeaconBlock {
fn slot(&self) -> u64 {
self.slot
}
fn parent_root(&self) -> Hash256 {
self.parent_root
}
fn state_root(&self) -> Hash256 {
self.state_root
}
fn canonical_root(&self) -> Hash256 {
self.canonical_root()
}
fn into_beacon_block(self) -> Option<BeaconBlock> {
Some(self)
}
}

View File

@ -0,0 +1,5 @@
mod block_reader;
mod state_reader;
pub use self::block_reader::BeaconBlockReader;
pub use self::state_reader::BeaconStateReader;

View File

@ -0,0 +1,30 @@
use crate::{BeaconState, Hash256};
use std::fmt::Debug;
/// The `BeaconStateReader` provides interfaces for reading a subset of fields of a `BeaconState`.
///
/// The purpose of this trait is to allow reading from either;
/// - a standard `BeaconState` struct, or
/// - a SSZ serialized byte array.
///
/// Note: presently, direct SSZ reading has not been implemented so this trait is being used for
/// "future proofing".
pub trait BeaconStateReader: Debug + PartialEq {
fn slot(&self) -> u64;
fn canonical_root(&self) -> Hash256;
fn into_beacon_state(self) -> Option<BeaconState>;
}
impl BeaconStateReader for BeaconState {
fn slot(&self) -> u64 {
self.slot
}
fn canonical_root(&self) -> Hash256 {
self.canonical_root()
}
fn into_beacon_state(self) -> Option<BeaconState> {
Some(self)
}
}

View File

@ -37,8 +37,7 @@ impl<T: RngCore> TestRandom<T> for ShardCommittee {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -48,8 +48,7 @@ impl<T: RngCore> TestRandom<T> for ShardReassignmentRecord {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -55,8 +55,7 @@ impl<T: RngCore> TestRandom<T> for SlashableVoteData {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -5,7 +5,7 @@ use rand::RngCore;
use ssz::{Decodable, DecodeError, Encodable, SszStream};
use std::convert;
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ValidatorStatus {
PendingActivation,
Active,
@ -89,7 +89,7 @@ impl<T: RngCore> TestRandom<T> for ValidatorStatus {
ValidatorStatus::Withdrawn,
ValidatorStatus::Penalized,
];
options[(rng.next_u32() as usize) % options.len()].clone()
options[(rng.next_u32() as usize) % options.len()]
}
}
@ -160,8 +160,7 @@ impl<T: RngCore> TestRandom<T> for ValidatorRecord {
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {

View File

@ -6,7 +6,7 @@ use bls_aggregates::AggregateSignature as RawAggregateSignature;
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Default)]
pub struct AggregateSignature(RawAggregateSignature);
impl AggregateSignature {

View File

@ -1,74 +0,0 @@
use std::time::{Duration, SystemTime, SystemTimeError};
pub fn slot_now(
genesis_seconds: u64,
slot_duration_seconds: u64,
) -> Result<Option<u64>, SystemTimeError> {
let sys_time = SystemTime::now();
let duration_since_epoch = sys_time.duration_since(SystemTime::UNIX_EPOCH)?;
let duration_since_genesis =
duration_since_epoch.checked_sub(Duration::from_secs(genesis_seconds));
match duration_since_genesis {
None => Ok(None),
Some(d) => Ok(slot_from_duration(slot_duration_seconds, d)),
}
}
fn slot_from_duration(slot_duration_seconds: u64, duration: Duration) -> Option<u64> {
duration.as_secs().checked_div(slot_duration_seconds)
}
#[cfg(test)]
mod tests {
use super::*;
/*
* Note: these tests are using actual system times and could fail if they are executed on a
* very slow machine.
*/
#[test]
fn test_slot_now() {
let s_time = 100;
let now = SystemTime::now();
let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
let genesis = since_epoch.as_secs() - s_time * 89;
assert_eq!(slot_now(genesis, s_time).unwrap(), Some(89));
let genesis = since_epoch.as_secs();
assert_eq!(slot_now(genesis, s_time).unwrap(), Some(0));
let genesis = since_epoch.as_secs() - s_time * 42 - 5;
assert_eq!(slot_now(genesis, s_time).unwrap(), Some(42));
}
#[test]
fn test_slot_from_duration() {
let s_time = 100;
assert_eq!(slot_from_duration(s_time, Duration::from_secs(0)), Some(0));
assert_eq!(slot_from_duration(s_time, Duration::from_secs(10)), Some(0));
assert_eq!(
slot_from_duration(s_time, Duration::from_secs(100)),
Some(1)
);
assert_eq!(
slot_from_duration(s_time, Duration::from_secs(101)),
Some(1)
);
assert_eq!(
slot_from_duration(s_time, Duration::from_secs(1000)),
Some(10)
);
}
#[test]
fn test_slot_from_duration_slot_time_zero() {
let s_time = 0;
assert_eq!(slot_from_duration(s_time, Duration::from_secs(0)), None);
assert_eq!(slot_from_duration(s_time, Duration::from_secs(10)), None);
assert_eq!(slot_from_duration(s_time, Duration::from_secs(1000)), None);
}
}

View File

@ -1,5 +1,5 @@
[package]
name = "slot-clock"
name = "slot_clock"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"

View File

@ -0,0 +1,11 @@
mod system_time_slot_clock;
mod testing_slot_clock;
pub use crate::system_time_slot_clock::{Error as SystemTimeSlotClockError, SystemTimeSlotClock};
pub use crate::testing_slot_clock::{Error as TestingSlotClockError, TestingSlotClock};
pub trait SlotClock {
type Error;
fn present_slot(&self) -> Result<Option<u64>, Self::Error>;
}

View File

@ -0,0 +1,135 @@
use super::SlotClock;
use std::time::{Duration, SystemTime};
pub use std::time::SystemTimeError;
#[derive(Debug)]
pub enum Error {
SlotDurationIsZero,
SystemTimeError(SystemTimeError),
}
/// Determines the present slot based upon the present system time.
pub struct SystemTimeSlotClock {
genesis_seconds: u64,
slot_duration_seconds: u64,
}
impl SystemTimeSlotClock {
/// Create a new `SystemTimeSlotClock`.
///
/// Returns an Error if `slot_duration_seconds == 0`.
pub fn new(
genesis_seconds: u64,
slot_duration_seconds: u64,
) -> Result<SystemTimeSlotClock, Error> {
if slot_duration_seconds == 0 {
Err(Error::SlotDurationIsZero)
} else {
Ok(Self {
genesis_seconds,
slot_duration_seconds,
})
}
}
}
impl SlotClock for SystemTimeSlotClock {
type Error = Error;
fn present_slot(&self) -> Result<Option<u64>, Error> {
let syslot_time = SystemTime::now();
let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH)?;
let duration_since_genesis =
duration_since_epoch.checked_sub(Duration::from_secs(self.genesis_seconds));
match duration_since_genesis {
None => Ok(None),
Some(d) => Ok(slot_from_duration(self.slot_duration_seconds, d)),
}
}
}
impl From<SystemTimeError> for Error {
fn from(e: SystemTimeError) -> Error {
Error::SystemTimeError(e)
}
}
fn slot_from_duration(slot_duration_seconds: u64, duration: Duration) -> Option<u64> {
duration.as_secs().checked_div(slot_duration_seconds)
}
#[cfg(test)]
mod tests {
use super::*;
/*
* Note: these tests are using actual system times and could fail if they are executed on a
* very slow machine.
*/
#[test]
fn test_slot_now() {
let slot_time = 100;
let now = SystemTime::now();
let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
let genesis = since_epoch.as_secs() - slot_time * 89;
let clock = SystemTimeSlotClock {
genesis_seconds: genesis,
slot_duration_seconds: slot_time,
};
assert_eq!(clock.present_slot().unwrap(), Some(89));
let clock = SystemTimeSlotClock {
genesis_seconds: since_epoch.as_secs(),
slot_duration_seconds: slot_time,
};
assert_eq!(clock.present_slot().unwrap(), Some(0));
let clock = SystemTimeSlotClock {
genesis_seconds: since_epoch.as_secs() - slot_time * 42 - 5,
slot_duration_seconds: slot_time,
};
assert_eq!(clock.present_slot().unwrap(), Some(42));
}
#[test]
fn test_slot_from_duration() {
let slot_time = 100;
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(0)),
Some(0)
);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(10)),
Some(0)
);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(100)),
Some(1)
);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(101)),
Some(1)
);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(1000)),
Some(10)
);
}
#[test]
fn test_slot_from_duration_slot_time_zero() {
let slot_time = 0;
assert_eq!(slot_from_duration(slot_time, Duration::from_secs(0)), None);
assert_eq!(slot_from_duration(slot_time, Duration::from_secs(10)), None);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(1000)),
None
);
}
}

View File

@ -0,0 +1,43 @@
use super::SlotClock;
#[derive(Debug, PartialEq)]
pub enum Error {}
/// Determines the present slot based upon the present system time.
pub struct TestingSlotClock {
slot: u64,
}
impl TestingSlotClock {
/// Create a new `TestingSlotClock`.
///
/// Returns an Error if `slot_duration_seconds == 0`.
pub fn new(slot: u64) -> TestingSlotClock {
TestingSlotClock { slot }
}
pub fn set_slot(&mut self, slot: u64) {
self.slot = slot;
}
}
impl SlotClock for TestingSlotClock {
type Error = Error;
fn present_slot(&self) -> Result<Option<u64>, Error> {
Ok(Some(self.slot))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slot_now() {
let mut clock = TestingSlotClock::new(10);
assert_eq!(clock.present_slot(), Ok(Some(10)));
clock.set_slot(123);
assert_eq!(clock.present_slot(), Ok(Some(123)));
}
}

View File

@ -0,0 +1,17 @@
[package]
name = "chain"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls = { path = "../../beacon_chain/utils/bls" }
db = { path = "../db" }
genesis = { path = "../../beacon_chain/genesis" }
naive_fork_choice = { path = "../../beacon_chain/naive_fork_choice" }
slot_clock = { path = "../../beacon_chain/utils/slot_clock" }
spec = { path = "../../beacon_chain/spec" }
ssz = { path = "../../beacon_chain/utils/ssz" }
types = { path = "../../beacon_chain/types" }
validator_induction = { path = "../../beacon_chain/validator_induction" }
validator_shuffling = { path = "../../beacon_chain/validator_shuffling" }

View File

@ -0,0 +1,72 @@
use super::{BeaconChain, ClientDB, DBError, SlotClock};
use slot_clock::TestingSlotClockError;
use ssz::{ssz_encode, Encodable};
use types::{readers::BeaconBlockReader, Hash256};
#[derive(Debug, PartialEq)]
pub enum Outcome {
FutureSlot,
Processed,
NewCanonicalBlock,
NewReorgBlock,
NewForkBlock,
}
#[derive(Debug, PartialEq)]
pub enum Error {
DBError(String),
NotImplemented,
PresentSlotIsNone,
}
impl<T, U> BeaconChain<T, U>
where
T: ClientDB,
U: SlotClock,
Error: From<<U as SlotClock>::Error>,
{
pub fn process_block<V>(&mut self, block: &V) -> Result<(Outcome, Hash256), Error>
where
V: BeaconBlockReader + Encodable + Sized,
{
let block_root = block.canonical_root();
let present_slot = self
.slot_clock
.present_slot()?
.ok_or(Error::PresentSlotIsNone)?;
// Block from future slots (i.e., greater than the present slot) should not be processed.
if block.slot() > present_slot {
return Ok((Outcome::FutureSlot, block_root));
}
// TODO: block processing has been removed.
// https://github.com/sigp/lighthouse/issues/98
// Update leaf blocks.
self.block_store.put(&block_root, &ssz_encode(block)[..])?;
if self.leaf_blocks.contains(&block.parent_root()) {
self.leaf_blocks.remove(&block.parent_root());
}
if self.canonical_leaf_block == block.parent_root() {
self.canonical_leaf_block = block_root;
}
self.leaf_blocks.insert(block_root);
Ok((Outcome::Processed, block_root))
}
}
impl From<DBError> for Error {
fn from(e: DBError) -> Error {
Error::DBError(e.message)
}
}
impl From<TestingSlotClockError> for Error {
fn from(_: TestingSlotClockError) -> Error {
unreachable!(); // Testing clock never throws an error.
}
}

View File

@ -0,0 +1,75 @@
use super::{BeaconChain, ClientDB, DBError, SlotClock};
use slot_clock::TestingSlotClockError;
use types::{
readers::{BeaconBlockReader, BeaconStateReader},
BeaconBlock, BeaconState, Hash256,
};
#[derive(Debug, PartialEq)]
pub enum Error {
DBError(String),
PresentSlotIsNone,
}
impl<T, U> BeaconChain<T, U>
where
T: ClientDB,
U: SlotClock,
Error: From<<U as SlotClock>::Error>,
{
pub fn produce_block(&mut self) -> Result<(BeaconBlock, BeaconState), Error> {
/*
* Important: this code is a big stub and only exists to ensure that tests pass.
*
* https://github.com/sigp/lighthouse/issues/107
*/
let present_slot = self
.slot_clock
.present_slot()?
.ok_or(Error::PresentSlotIsNone)?;
let parent_root = self.canonical_leaf_block;
let parent_block_reader = self
.block_store
.get_reader(&parent_root)?
.ok_or_else(|| Error::DBError("Block not found.".to_string()))?;
let parent_state_reader = self
.state_store
.get_reader(&parent_block_reader.state_root())?
.ok_or_else(|| Error::DBError("State not found.".to_string()))?;
let parent_block = parent_block_reader
.into_beacon_block()
.ok_or_else(|| Error::DBError("Bad parent block SSZ.".to_string()))?;
let mut block = BeaconBlock {
slot: present_slot,
parent_root,
state_root: Hash256::zero(), // Updated after the state is calculated.
..parent_block
};
let parent_state = parent_state_reader
.into_beacon_state()
.ok_or_else(|| Error::DBError("Bad parent block SSZ.".to_string()))?;
let state = BeaconState {
slot: present_slot,
..parent_state
};
let state_root = state.canonical_root();
block.state_root = state_root;
Ok((block, state))
}
}
impl From<DBError> for Error {
fn from(e: DBError) -> Error {
Error::DBError(e.message)
}
}
impl From<TestingSlotClockError> for Error {
fn from(_: TestingSlotClockError) -> Error {
unreachable!(); // Testing clock never throws an error.
}
}

View File

@ -0,0 +1,81 @@
mod block_processing;
mod block_production;
use db::{
stores::{BeaconBlockStore, BeaconStateStore},
ClientDB, DBError,
};
use genesis::{genesis_beacon_block, genesis_beacon_state, GenesisError};
use slot_clock::SlotClock;
use spec::ChainSpec;
use ssz::ssz_encode;
use std::collections::HashSet;
use std::sync::Arc;
use types::Hash256;
pub use crate::block_processing::Outcome as BlockProcessingOutcome;
#[derive(Debug, PartialEq)]
pub enum BeaconChainError {
InsufficientValidators,
GenesisError(GenesisError),
DBError(String),
}
pub struct BeaconChain<T: ClientDB + Sized, U: SlotClock> {
pub block_store: Arc<BeaconBlockStore<T>>,
pub state_store: Arc<BeaconStateStore<T>>,
pub slot_clock: U,
pub leaf_blocks: HashSet<Hash256>,
pub canonical_leaf_block: Hash256,
pub spec: ChainSpec,
}
impl<T, U> BeaconChain<T, U>
where
T: ClientDB,
U: SlotClock,
{
pub fn genesis(
state_store: Arc<BeaconStateStore<T>>,
block_store: Arc<BeaconBlockStore<T>>,
slot_clock: U,
spec: ChainSpec,
) -> Result<Self, BeaconChainError> {
if spec.initial_validators.is_empty() {
return Err(BeaconChainError::InsufficientValidators);
}
let genesis_state = genesis_beacon_state(&spec)?;
let state_root = genesis_state.canonical_root();
state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?;
let genesis_block = genesis_beacon_block(state_root, &spec);
let block_root = genesis_block.canonical_root();
block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?;
let mut leaf_blocks = HashSet::new();
leaf_blocks.insert(block_root);
Ok(Self {
block_store,
state_store,
slot_clock,
leaf_blocks,
canonical_leaf_block: block_root,
spec,
})
}
}
impl From<DBError> for BeaconChainError {
fn from(e: DBError) -> BeaconChainError {
BeaconChainError::DBError(e.message)
}
}
impl From<GenesisError> for BeaconChainError {
fn from(e: GenesisError) -> BeaconChainError {
BeaconChainError::GenesisError(e)
}
}

View File

@ -3,7 +3,7 @@ use db::ClientDB;
use state_transition::{extend_active_state, StateTransitionError};
use types::{ActiveState, BeaconBlock, CrystallizedState, Hash256};
impl<T> BeaconChain<T>
impl<T, U> BeaconChain<T, U>
where
T: ClientDB + Sized,
{

View File

@ -0,0 +1,49 @@
use chain::{BlockProcessingOutcome, BeaconChain};
use db::{
stores::{BeaconBlockStore, BeaconStateStore},
MemoryDB,
};
use slot_clock::TestingSlotClock;
use spec::ChainSpec;
use std::sync::Arc;
fn in_memory_test_stores() -> (
Arc<MemoryDB>,
Arc<BeaconBlockStore<MemoryDB>>,
Arc<BeaconStateStore<MemoryDB>>,
) {
let db = Arc::new(MemoryDB::open());
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
let state_store = Arc::new(BeaconStateStore::new(db.clone()));
(db, block_store, state_store)
}
fn in_memory_test_chain(
spec: ChainSpec,
) -> (Arc<MemoryDB>, BeaconChain<MemoryDB, TestingSlotClock>) {
let (db, block_store, state_store) = in_memory_test_stores();
let slot_clock = TestingSlotClock::new(0);
let chain = BeaconChain::genesis(state_store, block_store, slot_clock, spec);
(db, chain.unwrap())
}
#[test]
fn it_constructs() {
let (_db, _chain) = in_memory_test_chain(ChainSpec::foundation());
}
#[test]
fn it_produces() {
let (_db, mut chain) = in_memory_test_chain(ChainSpec::foundation());
let (_block, _state) = chain.produce_block().unwrap();
}
#[test]
fn it_processes_a_block_it_produces() {
let (_db, mut chain) = in_memory_test_chain(ChainSpec::foundation());
let (block, _state) = chain.produce_block().unwrap();
let (outcome, new_block_hash) = chain.process_block(&block).unwrap();
assert_eq!(outcome, BlockProcessingOutcome::Processed);
assert_eq!(chain.canonical_leaf_block, new_block_hash);
}

View File

@ -1,11 +1,8 @@
use super::BLOCKS_DB_COLUMN as DB_COLUMN;
use super::{ClientDB, DBError};
use ssz::{Decodable, DecodeError};
use ssz::Decodable;
use std::sync::Arc;
use types::Hash256;
type BeaconBlockHash = Vec<u8>;
type BeaconBlockSsz = Vec<u8>;
use types::{readers::BeaconBlockReader, BeaconBlock, Hash256};
#[derive(Clone, Debug, PartialEq)]
pub enum BeaconBlockAtSlotError {
@ -21,25 +18,29 @@ where
db: Arc<T>,
}
// Implements `put`, `get`, `exists` and `delete` for the store.
impl_crud_for_store!(BeaconBlockStore, DB_COLUMN);
impl<T: ClientDB> BeaconBlockStore<T> {
pub fn new(db: Arc<T>) -> Self {
Self { db }
}
pub fn put_serialized_block(&self, hash: &[u8], ssz: &[u8]) -> Result<(), DBError> {
self.db.put(DB_COLUMN, hash, ssz)
}
pub fn get_serialized_block(&self, hash: &[u8]) -> Result<Option<Vec<u8>>, DBError> {
self.db.get(DB_COLUMN, hash)
}
pub fn block_exists(&self, hash: &[u8]) -> Result<bool, DBError> {
self.db.exists(DB_COLUMN, hash)
}
pub fn delete_block(&self, hash: &[u8]) -> Result<(), DBError> {
self.db.delete(DB_COLUMN, hash)
/// Retuns an object implementing `BeaconBlockReader`, or `None` (if hash not known).
///
/// Note: Presently, this function fully deserializes a `BeaconBlock` and returns that. In the
/// future, it would be ideal to return an object capable of reading directly from serialized
/// SSZ bytes.
pub fn get_reader(&self, hash: &Hash256) -> Result<Option<impl BeaconBlockReader>, DBError> {
match self.get(&hash)? {
None => Ok(None),
Some(ssz) => {
let (block, _) = BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| DBError {
message: "Bad BeaconBlock SSZ.".to_string(),
})?;
Ok(Some(block))
}
}
}
/// Retrieve the block at a slot given a "head_hash" and a slot.
@ -51,37 +52,33 @@ impl<T: ClientDB> BeaconBlockStore<T> {
/// slot number. If the slot is skipped, the function will return None.
///
/// If a block is found, a tuple of (block_hash, serialized_block) is returned.
///
/// Note: this function uses a loop instead of recursion as the compiler is over-strict when it
/// comes to recursion and the `impl Trait` pattern. See:
/// https://stackoverflow.com/questions/54032940/using-impl-trait-in-a-recursive-function
pub fn block_at_slot(
&self,
head_hash: &[u8],
head_hash: &Hash256,
slot: u64,
) -> Result<Option<(BeaconBlockHash, BeaconBlockSsz)>, BeaconBlockAtSlotError> {
match self.get_serialized_block(head_hash)? {
None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock),
Some(ssz) => {
let (retrieved_slot, parent_hash) = slot_and_parent_from_block_ssz(&ssz, 0)
.map_err(|_| BeaconBlockAtSlotError::InvalidBeaconBlock)?;
match retrieved_slot {
s if s == slot => Ok(Some((head_hash.to_vec(), ssz.to_vec()))),
s if s < slot => Ok(None),
_ => self.block_at_slot(&parent_hash, slot),
) -> Result<Option<(Hash256, impl BeaconBlockReader)>, BeaconBlockAtSlotError> {
let mut current_hash = *head_hash;
loop {
if let Some(block_reader) = self.get_reader(&current_hash)? {
if block_reader.slot() == slot {
break Ok(Some((current_hash, block_reader)));
} else if block_reader.slot() < slot {
break Ok(None);
} else {
current_hash = block_reader.parent_root();
}
} else {
break Err(BeaconBlockAtSlotError::UnknownBeaconBlock);
}
}
}
}
/// Read `block.slot` and `block.parent_root` from a SSZ-encoded block bytes.
///
/// Assumes the block starts at byte `i`.
fn slot_and_parent_from_block_ssz(ssz: &[u8], i: usize) -> Result<(u64, Hash256), DecodeError> {
// Assuming the slot is the first field on a block.
let (slot, i) = u64::ssz_decode(&ssz, i)?;
// Assuming the parent has is the second field on a block.
let (parent_root, _) = Hash256::ssz_decode(&ssz, i)?;
Ok((slot, parent_root))
}
impl From<DBError> for BeaconBlockAtSlotError {
fn from(e: DBError) -> Self {
BeaconBlockAtSlotError::DBError(e.message)
@ -101,81 +98,22 @@ mod tests {
use types::BeaconBlock;
use types::Hash256;
#[test]
fn test_put_serialized_block() {
let db = Arc::new(MemoryDB::open());
let store = BeaconBlockStore::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
store.put_serialized_block(hash, ssz).unwrap();
assert_eq!(db.get(DB_COLUMN, hash).unwrap().unwrap(), ssz);
}
test_crud_for_store!(BeaconBlockStore, DB_COLUMN);
#[test]
fn test_get_serialized_block() {
fn head_hash_slot_too_low() {
let db = Arc::new(MemoryDB::open());
let store = BeaconBlockStore::new(db.clone());
let bs = Arc::new(BeaconBlockStore::new(db.clone()));
let mut rng = XorShiftRng::from_seed([42; 16]);
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
let mut block = BeaconBlock::random_for_test(&mut rng);
block.slot = 10;
db.put(DB_COLUMN, hash, ssz).unwrap();
assert_eq!(store.get_serialized_block(hash).unwrap().unwrap(), ssz);
}
let block_root = block.canonical_root();
bs.put(&block_root, &ssz_encode(&block)).unwrap();
#[test]
fn test_get_unknown_serialized_block() {
let db = Arc::new(MemoryDB::open());
let store = BeaconBlockStore::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
let other_hash = &Hash256::from("another hash".as_bytes()).to_vec();
db.put(DB_COLUMN, other_hash, ssz).unwrap();
assert_eq!(store.get_serialized_block(hash).unwrap(), None);
}
#[test]
fn test_block_exists() {
let db = Arc::new(MemoryDB::open());
let store = BeaconBlockStore::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
db.put(DB_COLUMN, hash, ssz).unwrap();
assert!(store.block_exists(hash).unwrap());
}
#[test]
fn test_block_does_not_exist() {
let db = Arc::new(MemoryDB::open());
let store = BeaconBlockStore::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
let other_hash = &Hash256::from("another hash".as_bytes()).to_vec();
db.put(DB_COLUMN, hash, ssz).unwrap();
assert!(!store.block_exists(other_hash).unwrap());
}
#[test]
fn test_delete_block() {
let db = Arc::new(MemoryDB::open());
let store = BeaconBlockStore::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
db.put(DB_COLUMN, hash, ssz).unwrap();
assert!(db.exists(DB_COLUMN, hash).unwrap());
store.delete_block(hash).unwrap();
assert!(!db.exists(DB_COLUMN, hash).unwrap());
let result = bs.block_at_slot(&block_root, 11).unwrap();
assert_eq!(result, None);
}
#[test]
@ -184,12 +122,14 @@ mod tests {
let store = BeaconBlockStore::new(db.clone());
let ssz = "definitly not a valid block".as_bytes();
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
let hash = &Hash256::from("some hash".as_bytes());
db.put(DB_COLUMN, hash, ssz).unwrap();
assert_eq!(
store.block_at_slot(hash, 42),
Err(BeaconBlockAtSlotError::InvalidBeaconBlock)
Err(BeaconBlockAtSlotError::DBError(
"Bad BeaconBlock SSZ.".into()
))
);
}
@ -199,8 +139,8 @@ mod tests {
let store = BeaconBlockStore::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
let other_hash = &Hash256::from("another hash".as_bytes()).to_vec();
let hash = &Hash256::from("some hash".as_bytes());
let other_hash = &Hash256::from("another hash".as_bytes());
db.put(DB_COLUMN, hash, ssz).unwrap();
assert_eq!(
@ -228,7 +168,7 @@ mod tests {
for w in 0..wc {
let key = (t * w) as u8;
let val = 42;
bs.put_serialized_block(&vec![key], &vec![val]).unwrap();
bs.put(&[key][..].into(), &vec![val]).unwrap();
}
});
handles.push(handle);
@ -241,8 +181,8 @@ mod tests {
for t in 0..thread_count {
for w in 0..write_count {
let key = (t * w) as u8;
assert!(bs.block_exists(&vec![key]).unwrap());
let val = bs.get_serialized_block(&vec![key]).unwrap().unwrap();
assert!(bs.exists(&[key][..].into()).unwrap());
let val = bs.get(&[key][..].into()).unwrap().unwrap();
assert_eq!(vec![42], val);
}
}
@ -281,13 +221,7 @@ mod tests {
block.slot = slots[i];
let ssz = ssz_encode(&block);
db.put(DB_COLUMN, &hashes[i].to_vec(), &ssz).unwrap();
// Ensure the slot and parent_root decoding fn works correctly.
let (decoded_slot, decoded_parent_root) =
slot_and_parent_from_block_ssz(&ssz, 0).unwrap();
assert_eq!(decoded_slot, block.slot);
assert_eq!(decoded_parent_root, block.parent_root);
db.put(DB_COLUMN, &hashes[i], &ssz).unwrap();
blocks.push(block);
}
@ -295,14 +229,12 @@ mod tests {
// Test that certain slots can be reached from certain hashes.
let test_cases = vec![(4, 4), (4, 3), (4, 2), (4, 1), (4, 0)];
for (hashes_index, slot_index) in test_cases {
let (matched_block_hash, matched_block_ssz) = bs
let (matched_block_hash, reader) = bs
.block_at_slot(&hashes[hashes_index], slots[slot_index])
.unwrap()
.unwrap();
let (retrieved_slot, _) =
slot_and_parent_from_block_ssz(&matched_block_ssz, 0).unwrap();
assert_eq!(retrieved_slot, slots[slot_index]);
assert_eq!(matched_block_hash, hashes[slot_index].to_vec());
assert_eq!(matched_block_hash, hashes[slot_index]);
assert_eq!(reader.slot(), slots[slot_index]);
}
let ssz = bs.block_at_slot(&hashes[4], 2).unwrap();

View File

@ -0,0 +1,68 @@
use super::STATES_DB_COLUMN as DB_COLUMN;
use super::{ClientDB, DBError};
use ssz::Decodable;
use std::sync::Arc;
use types::{readers::BeaconStateReader, BeaconState, Hash256};
pub struct BeaconStateStore<T>
where
T: ClientDB,
{
db: Arc<T>,
}
// Implements `put`, `get`, `exists` and `delete` for the store.
impl_crud_for_store!(BeaconStateStore, DB_COLUMN);
impl<T: ClientDB> BeaconStateStore<T> {
pub fn new(db: Arc<T>) -> Self {
Self { db }
}
/// Retuns an object implementing `BeaconStateReader`, or `None` (if hash not known).
///
/// Note: Presently, this function fully deserializes a `BeaconState` and returns that. In the
/// future, it would be ideal to return an object capable of reading directly from serialized
/// SSZ bytes.
pub fn get_reader(&self, hash: &Hash256) -> Result<Option<impl BeaconStateReader>, DBError> {
match self.get(&hash)? {
None => Ok(None),
Some(ssz) => {
let (state, _) = BeaconState::ssz_decode(&ssz, 0).map_err(|_| DBError {
message: "Bad State SSZ.".to_string(),
})?;
Ok(Some(state))
}
}
}
}
#[cfg(test)]
mod tests {
use super::super::super::MemoryDB;
use super::*;
use std::sync::Arc;
use ssz::ssz_encode;
use types::Hash256;
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
test_crud_for_store!(BeaconStateStore, DB_COLUMN);
#[test]
fn test_reader() {
let db = Arc::new(MemoryDB::open());
let store = BeaconStateStore::new(db.clone());
let mut rng = XorShiftRng::from_seed([42; 16]);
let state = BeaconState::random_for_test(&mut rng);
let state_root = state.canonical_root();
store.put(&state_root, &ssz_encode(&state)).unwrap();
let reader = store.get_reader(&state_root).unwrap().unwrap();
let decoded = reader.into_beacon_state().unwrap();
assert_eq!(state, decoded);
}
}

View File

@ -0,0 +1,103 @@
macro_rules! impl_crud_for_store {
($store: ident, $db_column: expr) => {
impl<T: ClientDB> $store<T> {
pub fn put(&self, hash: &Hash256, ssz: &[u8]) -> Result<(), DBError> {
self.db.put($db_column, hash, ssz)
}
pub fn get(&self, hash: &Hash256) -> Result<Option<Vec<u8>>, DBError> {
self.db.get($db_column, hash)
}
pub fn exists(&self, hash: &Hash256) -> Result<bool, DBError> {
self.db.exists($db_column, hash)
}
pub fn delete(&self, hash: &Hash256) -> Result<(), DBError> {
self.db.delete($db_column, hash)
}
}
};
}
#[allow(unused_macros)]
macro_rules! test_crud_for_store {
($store: ident, $db_column: expr) => {
#[test]
fn test_put() {
let db = Arc::new(MemoryDB::open());
let store = $store::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes());
store.put(hash, ssz).unwrap();
assert_eq!(db.get(DB_COLUMN, hash).unwrap().unwrap(), ssz);
}
#[test]
fn test_get() {
let db = Arc::new(MemoryDB::open());
let store = $store::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes());
db.put(DB_COLUMN, hash, ssz).unwrap();
assert_eq!(store.get(hash).unwrap().unwrap(), ssz);
}
#[test]
fn test_get_unknown() {
let db = Arc::new(MemoryDB::open());
let store = $store::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes());
let other_hash = &Hash256::from("another hash".as_bytes());
db.put(DB_COLUMN, other_hash, ssz).unwrap();
assert_eq!(store.get(hash).unwrap(), None);
}
#[test]
fn test_exists() {
let db = Arc::new(MemoryDB::open());
let store = $store::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes());
db.put(DB_COLUMN, hash, ssz).unwrap();
assert!(store.exists(hash).unwrap());
}
#[test]
fn test_block_does_not_exist() {
let db = Arc::new(MemoryDB::open());
let store = $store::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes());
let other_hash = &Hash256::from("another hash".as_bytes());
db.put(DB_COLUMN, hash, ssz).unwrap();
assert!(!store.exists(other_hash).unwrap());
}
#[test]
fn test_delete() {
let db = Arc::new(MemoryDB::open());
let store = $store::new(db.clone());
let ssz = "some bytes".as_bytes();
let hash = &Hash256::from("some hash".as_bytes());
db.put(DB_COLUMN, hash, ssz).unwrap();
assert!(db.exists(DB_COLUMN, hash).unwrap());
store.delete(hash).unwrap();
assert!(!db.exists(DB_COLUMN, hash).unwrap());
}
};
}

View File

@ -1,17 +1,27 @@
use super::{ClientDB, DBError};
#[macro_use]
mod macros;
mod beacon_block_store;
mod beacon_state_store;
mod pow_chain_store;
mod validator_store;
pub use self::beacon_block_store::{BeaconBlockAtSlotError, BeaconBlockStore};
pub use self::beacon_state_store::BeaconStateStore;
pub use self::pow_chain_store::PoWChainStore;
pub use self::validator_store::{ValidatorStore, ValidatorStoreError};
use super::bls;
pub const BLOCKS_DB_COLUMN: &str = "blocks";
pub const STATES_DB_COLUMN: &str = "states";
pub const POW_CHAIN_DB_COLUMN: &str = "powchain";
pub const VALIDATOR_DB_COLUMN: &str = "validator";
pub const COLUMNS: [&str; 3] = [BLOCKS_DB_COLUMN, POW_CHAIN_DB_COLUMN, VALIDATOR_DB_COLUMN];
pub const COLUMNS: [&str; 4] = [
BLOCKS_DB_COLUMN,
STATES_DB_COLUMN,
POW_CHAIN_DB_COLUMN,
VALIDATOR_DB_COLUMN,
];

View File

@ -26,9 +26,9 @@ impl<T: ClientDB> PoWChainStore<T> {
#[cfg(test)]
mod tests {
extern crate types;
use super::*;
use super::super::super::MemoryDB;
use super::*;
use self::types::Hash256;