Updates network branch to v0.5.0

This commit is contained in:
Age Manning
2019-03-18 18:17:37 +11:00
174 changed files with 7711 additions and 3924 deletions

View File

@@ -16,3 +16,4 @@ ctrlc = { version = "3.1.1", features = ["termination"] }
tokio = "0.1.15"
futures = "0.1.25"
exit-future = "0.1.3"
state_processing = { path = "../eth2/state_processing" }

View File

@@ -1,4 +1,3 @@
use log::trace;
use ssz::TreeHash;
use state_processing::per_block_processing::validate_attestation_without_signature;
use std::collections::{HashMap, HashSet};
@@ -86,34 +85,22 @@ impl AttestationAggregator {
free_attestation: &FreeAttestation,
spec: &ChainSpec,
) -> Result<Outcome, BeaconStateError> {
let attestation_duties = match state.attestation_slot_and_shard_for_validator(
free_attestation.validator_index as usize,
spec,
) {
Err(BeaconStateError::EpochCacheUninitialized(e)) => {
panic!("Attempted to access unbuilt cache {:?}.", e)
}
Err(BeaconStateError::EpochOutOfBounds) => invalid_outcome!(Message::TooOld),
Err(BeaconStateError::ShardOutOfBounds) => invalid_outcome!(Message::BadShard),
Err(e) => return Err(e),
Ok(None) => invalid_outcome!(Message::BadValidatorIndex),
Ok(Some(attestation_duties)) => attestation_duties,
};
let duties =
match state.get_attestation_duties(free_attestation.validator_index as usize, spec) {
Err(BeaconStateError::EpochCacheUninitialized(e)) => {
panic!("Attempted to access unbuilt cache {:?}.", e)
}
Err(BeaconStateError::EpochOutOfBounds) => invalid_outcome!(Message::TooOld),
Err(BeaconStateError::ShardOutOfBounds) => invalid_outcome!(Message::BadShard),
Err(e) => return Err(e),
Ok(None) => invalid_outcome!(Message::BadValidatorIndex),
Ok(Some(attestation_duties)) => attestation_duties,
};
let (slot, shard, committee_index) = attestation_duties;
trace!(
"slot: {}, shard: {}, committee_index: {}, val_index: {}",
slot,
shard,
committee_index,
free_attestation.validator_index
);
if free_attestation.data.slot != slot {
if free_attestation.data.slot != duties.slot {
invalid_outcome!(Message::BadSlot);
}
if free_attestation.data.shard != shard {
if free_attestation.data.shard != duties.shard {
invalid_outcome!(Message::BadShard);
}
@@ -143,7 +130,7 @@ impl AttestationAggregator {
if let Some(updated_attestation) = aggregate_attestation(
existing_attestation,
&free_attestation.signature,
committee_index as usize,
duties.committee_index as usize,
) {
self.store.insert(signable_message, updated_attestation);
valid_outcome!(Message::Aggregated);
@@ -154,7 +141,7 @@ impl AttestationAggregator {
let mut aggregate_signature = AggregateSignature::new();
aggregate_signature.add(&free_attestation.signature);
let mut aggregation_bitfield = Bitfield::new();
aggregation_bitfield.set(committee_index as usize, true);
aggregation_bitfield.set(duties.committee_index as usize, true);
let new_attestation = Attestation {
data: free_attestation.data.clone(),
aggregation_bitfield,
@@ -177,9 +164,13 @@ impl AttestationAggregator {
) -> Vec<Attestation> {
let mut known_attestation_data: HashSet<AttestationData> = HashSet::new();
state.latest_attestations.iter().for_each(|attestation| {
known_attestation_data.insert(attestation.data.clone());
});
state
.previous_epoch_attestations
.iter()
.chain(state.current_epoch_attestations.iter())
.for_each(|attestation| {
known_attestation_data.insert(attestation.data.clone());
});
self.store
.values()

View File

@@ -15,10 +15,7 @@ use state_processing::{
per_slot_processing, BlockProcessingError, SlotProcessingError,
};
use std::sync::Arc;
use types::{
readers::{BeaconBlockReader, BeaconStateReader},
*,
};
use types::*;
#[derive(Debug, PartialEq)]
pub enum ValidBlock {
@@ -73,45 +70,30 @@ where
F: ForkChoice,
{
/// Instantiate a new Beacon Chain, from genesis.
#[allow(clippy::too_many_arguments)] // Will be re-factored in the coming weeks.
pub fn genesis(
pub fn from_genesis(
state_store: Arc<BeaconStateStore<T>>,
block_store: Arc<BeaconBlockStore<T>>,
slot_clock: U,
genesis_time: u64,
latest_eth1_data: Eth1Data,
initial_validator_deposits: Vec<Deposit>,
mut genesis_state: BeaconState,
genesis_block: BeaconBlock,
spec: ChainSpec,
fork_choice: F,
) -> Result<Self, Error> {
if initial_validator_deposits.is_empty() {
return Err(Error::InsufficientValidators);
}
let mut genesis_state = BeaconState::genesis(
genesis_time,
initial_validator_deposits,
latest_eth1_data,
&spec,
)?;
let state_root = genesis_state.canonical_root();
state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?;
let genesis_block = BeaconBlock::genesis(state_root, &spec);
let block_root = genesis_block.canonical_root();
let block_root = genesis_block.into_header().canonical_root();
block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?;
let finalized_head = RwLock::new(CheckPoint::new(
genesis_block.clone(),
block_root,
// TODO: this is a memory waste; remove full clone.
genesis_state.clone(),
state_root,
));
let canonical_head = RwLock::new(CheckPoint::new(
genesis_block.clone(),
block_root,
// TODO: this is a memory waste; remove full clone.
genesis_state.clone(),
state_root,
));
@@ -119,7 +101,8 @@ where
genesis_state.build_epoch_cache(RelativeEpoch::Previous, &spec)?;
genesis_state.build_epoch_cache(RelativeEpoch::Current, &spec)?;
genesis_state.build_epoch_cache(RelativeEpoch::Next, &spec)?;
genesis_state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)?;
genesis_state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)?;
Ok(Self {
block_store,
@@ -205,10 +188,13 @@ where
/// processing applied to it.
pub fn advance_state(&self, slot: Slot) -> Result<(), SlotProcessingError> {
let state_slot = self.state.read().slot;
let head_block_root = self.head().beacon_block_root;
let latest_block_header = self.head().beacon_block.into_header();
for _ in state_slot.as_u64()..slot.as_u64() {
per_slot_processing(&mut *self.state.write(), head_block_root, &self.spec)?;
per_slot_processing(&mut *self.state.write(), &latest_block_header, &self.spec)?;
}
Ok(())
}
@@ -261,19 +247,15 @@ where
/// present and prior epoch is available.
pub fn block_proposer(&self, slot: Slot) -> Result<usize, BeaconStateError> {
trace!("BeaconChain::block_proposer: slot: {}", slot);
let index = self
.state
.read()
.get_beacon_proposer_index(slot, &self.spec)?;
let index = self.state.read().get_beacon_proposer_index(
slot,
RelativeEpoch::Current,
&self.spec,
)?;
Ok(index)
}
/// Returns the justified slot for the present state.
pub fn justified_epoch(&self) -> Epoch {
self.state.read().justified_epoch
}
/// Returns the attestation slot and shard for a given validator index.
///
/// Information is read from the current state, so only information from the present and prior
@@ -286,12 +268,12 @@ where
"BeaconChain::validator_attestion_slot_and_shard: validator_index: {}",
validator_index
);
if let Some((slot, shard, _committee)) = self
if let Some(attestation_duty) = self
.state
.read()
.attestation_slot_and_shard_for_validator(validator_index, &self.spec)?
.get_attestation_duties(validator_index, &self.spec)?
{
Ok(Some((slot, shard)))
Ok(Some((attestation_duty.slot, attestation_duty.shard)))
} else {
Ok(None)
}
@@ -300,37 +282,33 @@ where
/// Produce an `AttestationData` that is valid for the present `slot` and given `shard`.
pub fn produce_attestation_data(&self, shard: u64) -> Result<AttestationData, Error> {
trace!("BeaconChain::produce_attestation_data: shard: {}", shard);
let justified_epoch = self.justified_epoch();
let justified_block_root = *self
.state
.read()
.get_block_root(
justified_epoch.start_slot(self.spec.slots_per_epoch),
&self.spec,
)
.ok_or_else(|| Error::BadRecentBlockRoots)?;
let source_epoch = self.state.read().current_justified_epoch;
let source_root = *self.state.read().get_block_root(
source_epoch.start_slot(self.spec.slots_per_epoch),
&self.spec,
)?;
let epoch_boundary_root = *self
.state
.read()
.get_block_root(
self.state.read().current_epoch_start_slot(&self.spec),
&self.spec,
)
.ok_or_else(|| Error::BadRecentBlockRoots)?;
let target_root = *self.state.read().get_block_root(
self.state
.read()
.slot
.epoch(self.spec.slots_per_epoch)
.start_slot(self.spec.slots_per_epoch),
&self.spec,
)?;
Ok(AttestationData {
slot: self.state.read().slot,
shard,
beacon_block_root: self.head().beacon_block_root,
epoch_boundary_root,
target_root,
crosslink_data_root: Hash256::zero(),
latest_crosslink: Crosslink {
previous_crosslink: Crosslink {
epoch: self.state.read().slot.epoch(self.spec.slots_per_epoch),
crosslink_data_root: Hash256::zero(),
},
justified_epoch,
justified_block_root,
source_epoch,
source_root,
})
}
@@ -577,66 +555,13 @@ where
}
}
/// Dumps the entire canonical chain, from the head to genesis to a vector for analysis.
///
/// This could be a very expensive operation and should only be done in testing/analysis
/// activities.
pub fn chain_dump(&self) -> Result<Vec<CheckPoint>, Error> {
let mut dump = vec![];
let mut last_slot = CheckPoint {
beacon_block: self.head().beacon_block.clone(),
beacon_block_root: self.head().beacon_block_root,
beacon_state: self.head().beacon_state.clone(),
beacon_state_root: self.head().beacon_state_root,
};
dump.push(last_slot.clone());
loop {
let beacon_block_root = last_slot.beacon_block.parent_root;
if beacon_block_root == self.spec.zero_hash {
break; // Genesis has been reached.
}
let beacon_block = self
.block_store
.get_deserialized(&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
.state_store
.get_deserialized(&beacon_state_root)?
.ok_or_else(|| {
Error::DBInconsistent(format!("Missing state {}", beacon_state_root))
})?;
let slot = CheckPoint {
beacon_block,
beacon_block_root,
beacon_state,
beacon_state_root,
};
dump.push(slot.clone());
last_slot = slot;
}
dump.reverse();
Ok(dump)
}
/// Accept some block and attempt to add it to block DAG.
///
/// Will accept blocks from prior slots, however it will reject any block from a future slot.
pub fn process_block(&self, block: BeaconBlock) -> Result<BlockProcessingOutcome, Error> {
debug!("Processing block with slot {}...", block.slot());
debug!("Processing block with slot {}...", block.slot);
let block_root = block.canonical_root();
let block_root = block.into_header().canonical_root();
let present_slot = self.present_slot();
@@ -648,9 +573,9 @@ where
// Load the blocks parent block from the database, returning invalid if that block is not
// found.
let parent_block_root = block.parent_root;
let parent_block = match self.block_store.get_reader(&parent_block_root)? {
Some(parent_root) => parent_root,
let parent_block_root = block.previous_block_root;
let parent_block = match self.block_store.get_deserialized(&parent_block_root)? {
Some(previous_block_root) => previous_block_root,
None => {
return Ok(BlockProcessingOutcome::InvalidBlock(
InvalidBlock::ParentUnknown,
@@ -660,23 +585,21 @@ where
// Load the parent blocks state from the database, returning an error if it is not found.
// It is an error because if 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
.state_store
.get_reader(&parent_state_root)?
.ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))?
.into_beacon_state()
.ok_or_else(|| {
Error::DBInconsistent(format!("State SSZ invalid {}", parent_state_root))
})?;
.get_deserialized(&parent_state_root)?
.ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))?;
// TODO: check the block proposer signature BEFORE doing a state transition. This will
// significantly lower exposure surface to DoS attacks.
// Transition the parent state to the present slot.
let mut state = parent_state;
println!("parent process state: {:?}", state.latest_block_header);
let previous_block_header = parent_block.into_header();
for _ in state.slot.as_u64()..present_slot.as_u64() {
if let Err(e) = per_slot_processing(&mut state, parent_block_root, &self.spec) {
if let Err(e) = per_slot_processing(&mut state, &previous_block_header, &self.spec) {
return Ok(BlockProcessingOutcome::InvalidBlock(
InvalidBlock::SlotProcessingError(e),
));
@@ -691,6 +614,8 @@ where
));
}
println!("process state: {:?}", state.latest_block_header);
let state_root = state.canonical_root();
if block.state_root != state_root {
@@ -752,22 +677,22 @@ where
attestations.len()
);
let parent_root = *state
.get_block_root(state.slot.saturating_sub(1_u64), &self.spec)
.ok_or_else(|| BlockProductionError::UnableToGetBlockRootFromState)?;
let previous_block_root = *state
.get_block_root(state.slot - 1, &self.spec)
.map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?;
let mut block = BeaconBlock {
slot: state.slot,
parent_root,
previous_block_root,
state_root: Hash256::zero(), // Updated after the state is calculated.
randao_reveal,
eth1_data: Eth1Data {
// TODO: replace with real data
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
},
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
body: BeaconBlockBody {
randao_reveal,
eth1_data: Eth1Data {
// TODO: replace with real data
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
},
proposer_slashings: self.get_proposer_slashings_for_block(),
attester_slashings: self.get_attester_slashings_for_block(),
attestations,
@@ -781,6 +706,8 @@ where
per_block_processing_without_verifying_block_signature(&mut state, &block, &self.spec)?;
println!("produce state: {:?}", state.latest_block_header);
let state_root = state.canonical_root();
block.state_root = state_root;
@@ -815,6 +742,59 @@ where
Ok(())
}
/// Dumps the entire canonical chain, from the head to genesis to a vector for analysis.
///
/// This could be a very expensive operation and should only be done in testing/analysis
/// activities.
pub fn chain_dump(&self) -> Result<Vec<CheckPoint>, Error> {
let mut dump = vec![];
let mut last_slot = CheckPoint {
beacon_block: self.head().beacon_block.clone(),
beacon_block_root: self.head().beacon_block_root,
beacon_state: self.head().beacon_state.clone(),
beacon_state_root: self.head().beacon_state_root,
};
dump.push(last_slot.clone());
loop {
let beacon_block_root = last_slot.beacon_block.previous_block_root;
if beacon_block_root == self.spec.zero_hash {
break; // Genesis has been reached.
}
let beacon_block = self
.block_store
.get_deserialized(&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
.state_store
.get_deserialized(&beacon_state_root)?
.ok_or_else(|| {
Error::DBInconsistent(format!("Missing state {}", beacon_state_root))
})?;
let slot = CheckPoint {
beacon_block,
beacon_block_root,
beacon_state,
beacon_state_root,
};
dump.push(slot.clone());
last_slot = slot;
}
dump.reverse();
Ok(dump)
}
}
impl From<DBError> for Error {

View File

@@ -3,19 +3,20 @@
// testnet. These are examples. Also. there is code duplication which can/should be cleaned up.
use crate::BeaconChain;
use bls;
use db::stores::{BeaconBlockStore, BeaconStateStore};
use db::{DiskDB, MemoryDB};
use fork_choice::BitwiseLMDGhost;
use slot_clock::SystemTimeSlotClock;
use ssz::TreeHash;
use std::path::PathBuf;
use std::sync::Arc;
use types::{ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, Hash256, Keypair};
use types::test_utils::TestingBeaconStateBuilder;
use types::{BeaconBlock, ChainSpec, Hash256};
//TODO: Correct this for prod
//TODO: Account for historical db
pub fn initialise_beacon_chain(
chain_spec: &ChainSpec,
spec: &ChainSpec,
db_name: Option<&PathBuf>,
) -> Arc<BeaconChain<DiskDB, SystemTimeSlotClock, BitwiseLMDGhost<DiskDB>>> {
// set up the db
@@ -23,124 +24,71 @@ pub fn initialise_beacon_chain(
db_name.expect("Database directory must be included"),
None,
));
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
let state_store = Arc::new(BeaconStateStore::new(db.clone()));
let state_builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec);
let (genesis_state, _keypairs) = state_builder.build();
let mut genesis_block = BeaconBlock::empty(&spec);
genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root());
// Slot clock
let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past).
let slot_clock = SystemTimeSlotClock::new(genesis_time, chain_spec.seconds_per_slot)
let slot_clock = SystemTimeSlotClock::new(genesis_state.genesis_time, spec.seconds_per_slot)
.expect("Unable to load SystemTimeSlotClock");
// Choose the fork choice
let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone());
/*
* Generate some random data to start a chain with.
*
* This is will need to be replace for production usage.
*/
let latest_eth1_data = Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
};
let keypairs: Vec<Keypair> = (0..10)
.collect::<Vec<usize>>()
.iter()
.map(|_| Keypair::random())
.collect();
let initial_validator_deposits = keypairs
.iter()
.map(|keypair| Deposit {
branch: vec![], // branch verification is not chain_specified.
index: 0, // index verification is not chain_specified.
deposit_data: DepositData {
amount: 32_000_000_000, // 32 ETH (in Gwei)
timestamp: genesis_time - 1,
deposit_input: DepositInput {
pubkey: keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(), // Withdrawal not possible.
proof_of_possession: bls::create_proof_of_possession(&keypair),
},
},
})
.collect();
// Genesis chain
// TODO:Remove the expect here. Propagate errors and handle somewhat gracefully.
//TODO: Handle error correctly
Arc::new(
BeaconChain::genesis(
BeaconChain::from_genesis(
state_store.clone(),
block_store.clone(),
slot_clock,
genesis_time,
latest_eth1_data,
initial_validator_deposits,
chain_spec.clone(),
genesis_state,
genesis_block,
spec.clone(),
fork_choice,
)
.expect("Cannot initialise a beacon chain. Exiting"),
.expect("Terminate if beacon chain generation fails"),
)
}
/// Initialisation of a test beacon chain, uses an in memory db with fixed genesis time.
pub fn initialise_test_beacon_chain(
chain_spec: &ChainSpec,
spec: &ChainSpec,
_db_name: Option<&PathBuf>,
) -> Arc<BeaconChain<MemoryDB, SystemTimeSlotClock, BitwiseLMDGhost<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()));
let state_builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, spec);
let (genesis_state, _keypairs) = state_builder.build();
let mut genesis_block = BeaconBlock::empty(spec);
genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root());
// Slot clock
let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past).
let slot_clock = SystemTimeSlotClock::new(genesis_time, chain_spec.seconds_per_slot)
let slot_clock = SystemTimeSlotClock::new(genesis_state.genesis_time, spec.seconds_per_slot)
.expect("Unable to load SystemTimeSlotClock");
// Choose the fork choice
let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone());
/*
* Generate some random data to start a chain with.
*
* This is will need to be replace for production usage.
*/
let latest_eth1_data = Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
};
let keypairs: Vec<Keypair> = (0..8)
.collect::<Vec<usize>>()
.iter()
.map(|_| Keypair::random())
.collect();
let initial_validator_deposits = keypairs
.iter()
.map(|keypair| Deposit {
branch: vec![], // branch verification is not chain_specified.
index: 0, // index verification is not chain_specified.
deposit_data: DepositData {
amount: 32_000_000_000, // 32 ETH (in Gwei)
timestamp: genesis_time - 1,
deposit_input: DepositInput {
pubkey: keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(), // Withdrawal not possible.
proof_of_possession: bls::create_proof_of_possession(&keypair),
},
},
})
.collect();
// Genesis chain
// TODO: Handle error correctly
//TODO: Handle error correctly
Arc::new(
BeaconChain::genesis(
BeaconChain::from_genesis(
state_store.clone(),
block_store.clone(),
slot_clock,
genesis_time,
latest_eth1_data,
initial_validator_deposits,
chain_spec.clone(),
genesis_state,
genesis_block,
spec.clone(),
fork_choice,
)
.expect("Cannot generate beacon chain"),
.expect("Terminate if beacon chain generation fails"),
)
}

View File

@@ -12,12 +12,7 @@ path = "src/bin.rs"
name = "test_harness"
path = "src/lib.rs"
[[bench]]
name = "state_transition"
harness = false
[dev-dependencies]
criterion = "0.2"
state_processing = { path = "../../../eth2/state_processing" }
[dependencies]
@@ -33,12 +28,14 @@ failure = "0.1"
failure_derive = "0.1"
fork_choice = { path = "../../../eth2/fork_choice" }
hashing = { path = "../../../eth2/utils/hashing" }
int_to_bytes = { path = "../../../eth2/utils/int_to_bytes" }
log = "0.4"
env_logger = "0.6.0"
rayon = "1.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde_yaml = "0.8"
slot_clock = { path = "../../../eth2/utils/slot_clock" }
ssz = { path = "../../../eth2/utils/ssz" }
types = { path = "../../../eth2/types" }

View File

@@ -1,69 +0,0 @@
use criterion::Criterion;
use criterion::{black_box, criterion_group, criterion_main, Benchmark};
// use env_logger::{Builder, Env};
use state_processing::SlotProcessable;
use test_harness::BeaconChainHarness;
use types::{ChainSpec, Hash256};
fn mid_epoch_state_transition(c: &mut Criterion) {
// Builder::from_env(Env::default().default_filter_or("debug")).init();
let validator_count = 1000;
let mut rig = BeaconChainHarness::new(ChainSpec::foundation(), validator_count);
let epoch_depth = (rig.spec.slots_per_epoch * 2) + (rig.spec.slots_per_epoch / 2);
for _ in 0..epoch_depth {
rig.advance_chain_with_block();
}
let state = rig.beacon_chain.state.read().clone();
assert!((state.slot + 1) % rig.spec.slots_per_epoch != 0);
c.bench_function("mid-epoch state transition 10k validators", move |b| {
let state = state.clone();
b.iter(|| {
let mut state = state.clone();
black_box(state.per_slot_processing(Hash256::zero(), &rig.spec))
})
});
}
fn epoch_boundary_state_transition(c: &mut Criterion) {
// Builder::from_env(Env::default().default_filter_or("debug")).init();
let validator_count = 10000;
let mut rig = BeaconChainHarness::new(ChainSpec::foundation(), validator_count);
let epoch_depth = rig.spec.slots_per_epoch * 2;
for _ in 0..(epoch_depth - 1) {
rig.advance_chain_with_block();
}
let state = rig.beacon_chain.state.read().clone();
assert_eq!((state.slot + 1) % rig.spec.slots_per_epoch, 0);
c.bench(
"routines",
Benchmark::new("routine_1", move |b| {
let state = state.clone();
b.iter(|| {
let mut state = state.clone();
black_box(black_box(
state.per_slot_processing(Hash256::zero(), &rig.spec),
))
})
})
.sample_size(5), // sample size is low because function is sloooow.
);
}
criterion_group!(
benches,
mid_epoch_state_transition,
epoch_boundary_state_transition
);
criterion_main!(benches);

View File

@@ -1,7 +1,6 @@
use super::ValidatorHarness;
use beacon_chain::{BeaconChain, BlockProcessingOutcome};
pub use beacon_chain::{BeaconChainError, CheckPoint};
use bls::{create_proof_of_possession, get_withdrawal_credentials};
use db::{
stores::{BeaconBlockStore, BeaconStateStore},
MemoryDB,
@@ -10,10 +9,11 @@ use fork_choice::BitwiseLMDGhost;
use log::debug;
use rayon::prelude::*;
use slot_clock::TestingSlotClock;
use ssz::TreeHash;
use std::collections::HashSet;
use std::iter::FromIterator;
use std::sync::Arc;
use types::*;
use types::{test_utils::TestingBeaconStateBuilder, *};
/// The beacon chain harness simulates a single beacon node with `validator_count` validators connected
/// to it. Each validator is provided a borrow to the beacon chain, where it may read
@@ -39,58 +39,24 @@ impl BeaconChainHarness {
let db = Arc::new(MemoryDB::open());
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
let state_store = Arc::new(BeaconStateStore::new(db.clone()));
let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past).
let slot_clock = TestingSlotClock::new(spec.genesis_slot.as_u64());
let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone());
let latest_eth1_data = Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
};
debug!("Generating validator keypairs...");
let state_builder =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
let (genesis_state, keypairs) = state_builder.build();
let keypairs: Vec<Keypair> = (0..validator_count)
.collect::<Vec<usize>>()
.par_iter()
.map(|_| Keypair::random())
.collect();
debug!("Creating validator deposits...");
let initial_validator_deposits = keypairs
.par_iter()
.map(|keypair| Deposit {
branch: vec![], // branch verification is not specified.
index: 0, // index verification is not specified.
deposit_data: DepositData {
amount: 32_000_000_000, // 32 ETH (in Gwei)
timestamp: genesis_time - 1,
deposit_input: DepositInput {
pubkey: keypair.pk.clone(),
// Validator can withdraw using their main keypair.
withdrawal_credentials: Hash256::from_slice(
&get_withdrawal_credentials(
&keypair.pk,
spec.bls_withdrawal_prefix_byte,
)[..],
),
proof_of_possession: create_proof_of_possession(&keypair),
},
},
})
.collect();
debug!("Creating the BeaconChain...");
let mut genesis_block = BeaconBlock::empty(&spec);
genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root());
// Create the Beacon Chain
let beacon_chain = Arc::new(
BeaconChain::genesis(
BeaconChain::from_genesis(
state_store.clone(),
block_store.clone(),
slot_clock,
genesis_time,
latest_eth1_data,
initial_validator_deposits,
genesis_state,
genesis_block,
spec.clone(),
fork_choice,
)
@@ -161,8 +127,8 @@ impl BeaconChainHarness {
.get_crosslink_committees_at_slot(present_slot, &self.spec)
.unwrap()
.iter()
.fold(vec![], |mut acc, (committee, _slot)| {
acc.append(&mut committee.clone());
.fold(vec![], |mut acc, c| {
acc.append(&mut c.committee.clone());
acc
});
let attesting_validators: HashSet<usize> =
@@ -267,6 +233,27 @@ impl BeaconChainHarness {
Some(Signature::new(message, domain, &validator.keypair.sk))
}
/// Returns the current `Fork` of the `beacon_chain`.
pub fn fork(&self) -> Fork {
self.beacon_chain.state.read().fork.clone()
}
/// Returns the current `epoch` of the `beacon_chain`.
pub fn epoch(&self) -> Epoch {
self.beacon_chain
.state
.read()
.slot
.epoch(self.spec.slots_per_epoch)
}
/// Returns the keypair for some validator index.
pub fn validator_keypair(&self, validator_index: usize) -> Option<&Keypair> {
self.validators
.get(validator_index)
.and_then(|v| Some(&v.keypair))
}
/// Submit a deposit to the `BeaconChain` and, if given a keypair, create a new
/// `ValidatorHarness` instance for this validator.
///

View File

@@ -1,69 +1,102 @@
use clap::{App, Arg};
use clap::{App, Arg, SubCommand};
use env_logger::{Builder, Env};
use std::{fs::File, io::prelude::*};
use test_case::TestCase;
use yaml_rust::YamlLoader;
use gen_keys::gen_keys;
use run_test::run_test;
use std::fs;
use types::test_utils::keypairs_path;
use types::ChainSpec;
mod beacon_chain_harness;
mod gen_keys;
mod run_test;
mod test_case;
mod validator_harness;
use validator_harness::ValidatorHarness;
fn main() {
let validator_file_path = keypairs_path();
let _ = fs::create_dir(validator_file_path.parent().unwrap());
let matches = App::new("Lighthouse Test Harness Runner")
.version("0.0.1")
.author("Sigma Prime <contact@sigmaprime.io>")
.about("Runs `test_harness` using a YAML test_case.")
.arg(
Arg::with_name("yaml")
.long("yaml")
.value_name("FILE")
.help("YAML file test_case.")
.required(true),
)
.arg(
Arg::with_name("log")
.long("log-level")
.short("l")
.value_name("LOG_LEVEL")
.help("Logging level.")
.possible_values(&["error", "warn", "info", "debug", "trace"])
.default_value("debug")
.required(true),
)
.arg(
Arg::with_name("spec")
.long("spec")
.short("s")
.value_name("SPECIFICATION")
.help("ChainSpec instantiation.")
.possible_values(&["foundation", "few_validators"])
.default_value("foundation"),
)
.subcommand(
SubCommand::with_name("run_test")
.about("Executes a YAML test specification")
.arg(
Arg::with_name("yaml")
.long("yaml")
.value_name("FILE")
.help("YAML file test_case.")
.required(true),
)
.arg(
Arg::with_name("validators_dir")
.long("validators-dir")
.short("v")
.value_name("VALIDATORS_DIR")
.help("A directory with validator deposits and keypair YAML."),
),
)
.subcommand(
SubCommand::with_name("gen_keys")
.about("Builds a file of BLS keypairs for faster tests.")
.arg(
Arg::with_name("validator_count")
.long("validator_count")
.short("n")
.value_name("VALIDATOR_COUNT")
.help("Number of validators to generate.")
.required(true),
)
.arg(
Arg::with_name("output_file")
.long("output_file")
.short("d")
.value_name("GENESIS_TIME")
.help("Output directory for generated YAML.")
.default_value(validator_file_path.to_str().unwrap()),
),
)
.get_matches();
if let Some(log_level) = matches.value_of("log") {
Builder::from_env(Env::default().default_filter_or(log_level)).init();
}
if let Some(yaml_file) = matches.value_of("yaml") {
let docs = {
let mut file = File::open(yaml_file).unwrap();
let _spec = match matches.value_of("spec") {
Some("foundation") => ChainSpec::foundation(),
Some("few_validators") => ChainSpec::few_validators(),
_ => unreachable!(), // Has a default value, should always exist.
};
let mut yaml_str = String::new();
file.read_to_string(&mut yaml_str).unwrap();
if let Some(matches) = matches.subcommand_matches("run_test") {
run_test(matches);
}
YamlLoader::load_from_str(&yaml_str).unwrap()
};
for doc in &docs {
// For each `test_cases` YAML in the document, build a `TestCase`, execute it and
// assert that the execution result matches the test_case description.
//
// In effect, for each `test_case` a new `BeaconChainHarness` is created from genesis
// and a new `BeaconChain` is built as per the test_case.
//
// After the `BeaconChain` has been built out as per the test_case, a dump of all blocks
// and states in the chain is obtained and checked against the `results` specified in
// the `test_case`.
//
// If any of the expectations in the results are not met, the process
// panics with a message.
for test_case in doc["test_cases"].as_vec().unwrap() {
let test_case = TestCase::from_yaml(test_case);
test_case.assert_result_valid(test_case.execute())
}
}
if let Some(matches) = matches.subcommand_matches("gen_keys") {
gen_keys(matches);
}
}

View File

@@ -0,0 +1,21 @@
use clap::{value_t, ArgMatches};
use log::debug;
use std::path::Path;
use types::test_utils::{generate_deterministic_keypairs, KeypairsFile};
/// Creates a file containing BLS keypairs.
pub fn gen_keys(matches: &ArgMatches) {
let validator_count = value_t!(matches.value_of("validator_count"), usize)
.expect("Validator count is required argument");
let output_file = matches
.value_of("output_file")
.expect("Output file has a default value.");
let keypairs = generate_deterministic_keypairs(validator_count);
debug!("Writing keypairs to file...");
let keypairs_path = Path::new(output_file);
keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap();
}

View File

@@ -0,0 +1,37 @@
use crate::test_case::TestCase;
use clap::ArgMatches;
use std::{fs::File, io::prelude::*};
use yaml_rust::YamlLoader;
/// Runs a YAML-specified test case.
pub fn run_test(matches: &ArgMatches) {
if let Some(yaml_file) = matches.value_of("yaml") {
let docs = {
let mut file = File::open(yaml_file).unwrap();
let mut yaml_str = String::new();
file.read_to_string(&mut yaml_str).unwrap();
YamlLoader::load_from_str(&yaml_str).unwrap()
};
for doc in &docs {
// For each `test_cases` YAML in the document, build a `TestCase`, execute it and
// assert that the execution result matches the test_case description.
//
// In effect, for each `test_case` a new `BeaconChainHarness` is created from genesis
// and a new `BeaconChain` is built as per the test_case.
//
// After the `BeaconChain` has been built out as per the test_case, a dump of all blocks
// and states in the chain is obtained and checked against the `results` specified in
// the `test_case`.
//
// If any of the expectations in the results are not met, the process
// panics with a message.
for test_case in doc["test_cases"].as_vec().unwrap() {
let test_case = TestCase::from_yaml(test_case);
test_case.assert_result_valid(test_case.execute())
}
}
}
}

View File

@@ -3,14 +3,11 @@
use crate::beacon_chain_harness::BeaconChainHarness;
use beacon_chain::CheckPoint;
use bls::{create_proof_of_possession, get_withdrawal_credentials};
use log::{info, warn};
use ssz::SignedRoot;
use types::*;
use types::{
attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder,
};
use types::test_utils::*;
use yaml_rust::Yaml;
mod config;
@@ -224,27 +221,20 @@ impl TestCase {
}
/// Builds a `Deposit` this is valid for the given `BeaconChainHarness` at its next slot.
fn build_transfer(harness: &BeaconChainHarness, from: u64, to: u64, amount: u64) -> Transfer {
fn build_transfer(
harness: &BeaconChainHarness,
sender: u64,
recipient: u64,
amount: u64,
) -> Transfer {
let slot = harness.beacon_chain.state.read().slot + 1;
let mut transfer = Transfer {
from,
to,
amount,
fee: 0,
slot,
pubkey: harness.validators[from as usize].keypair.pk.clone(),
signature: Signature::empty_signature(),
};
let mut builder = TestingTransferBuilder::new(sender, recipient, amount, slot);
let message = transfer.signed_root();
let epoch = slot.epoch(harness.spec.slots_per_epoch);
let keypair = harness.validator_keypair(sender as usize).unwrap();
builder.sign(keypair.clone(), &harness.fork(), &harness.spec);
transfer.signature = harness
.validator_sign(from as usize, &message[..], epoch, Domain::Transfer)
.expect("Unable to sign Transfer");
transfer
builder.build()
}
/// Builds a `Deposit` this is valid for the given `BeaconChainHarness`.
@@ -257,29 +247,12 @@ fn build_deposit(
index_offset: u64,
) -> (Deposit, Keypair) {
let keypair = Keypair::random();
let proof_of_possession = create_proof_of_possession(&keypair);
let index = harness.beacon_chain.state.read().deposit_index + index_offset;
let withdrawal_credentials = Hash256::from_slice(
&get_withdrawal_credentials(&keypair.pk, harness.spec.bls_withdrawal_prefix_byte)[..],
);
let deposit = Deposit {
// Note: `branch` and `index` will need to be updated once the spec defines their
// validity.
branch: vec![],
index,
deposit_data: DepositData {
amount,
timestamp: 1,
deposit_input: DepositInput {
pubkey: keypair.pk.clone(),
withdrawal_credentials,
proof_of_possession,
},
},
};
let mut builder = TestingDepositBuilder::new(keypair.pk.clone(), amount);
builder.set_index(harness.beacon_chain.state.read().deposit_index + index_offset);
builder.sign(&keypair, harness.epoch(), &harness.fork(), &harness.spec);
(deposit, keypair)
(builder.build(), keypair)
}
/// Builds a `VoluntaryExit` this is valid for the given `BeaconChainHarness`.
@@ -318,7 +291,7 @@ fn build_double_vote_attester_slashing(
.expect("Unable to sign AttesterSlashing")
};
AttesterSlashingBuilder::double_vote(validator_indices, signer)
TestingAttesterSlashingBuilder::double_vote(validator_indices, signer)
}
/// Builds an `ProposerSlashing` for some `validator_index`.
@@ -331,5 +304,5 @@ fn build_proposer_slashing(harness: &BeaconChainHarness, validator_index: u64) -
.expect("Unable to sign AttesterSlashing")
};
ProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec)
TestingProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec)
}

View File

@@ -2,7 +2,7 @@ use super::BLOCKS_DB_COLUMN as DB_COLUMN;
use super::{ClientDB, DBError};
use ssz::Decodable;
use std::sync::Arc;
use types::{readers::BeaconBlockReader, BeaconBlock, Hash256, Slot};
use types::{BeaconBlock, Hash256, Slot};
#[derive(Clone, Debug, PartialEq)]
pub enum BeaconBlockAtSlotError {
@@ -38,23 +38,6 @@ impl<T: ClientDB> BeaconBlockStore<T> {
}
}
/// Returns 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.
///
/// A "head_hash" must be a block hash with a slot number greater than or equal to the desired
@@ -72,17 +55,17 @@ impl<T: ClientDB> BeaconBlockStore<T> {
&self,
head_hash: &Hash256,
slot: Slot,
) -> Result<Option<(Hash256, impl BeaconBlockReader)>, BeaconBlockAtSlotError> {
) -> Result<Option<(Hash256, BeaconBlock)>, 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 {
if let Some(block) = self.get_deserialized(&current_hash)? {
if block.slot == slot {
break Ok(Some((current_hash, block)));
} else if block.slot < slot {
break Ok(None);
} else {
current_hash = block_reader.parent_root();
current_hash = block.previous_block_root;
}
} else {
break Err(BeaconBlockAtSlotError::UnknownBeaconBlock(current_hash));
@@ -198,6 +181,7 @@ mod tests {
}
#[test]
#[ignore]
fn test_block_at_slot() {
let db = Arc::new(MemoryDB::open());
let bs = Arc::new(BeaconBlockStore::new(db.clone()));
@@ -227,7 +211,7 @@ mod tests {
for i in 0..block_count {
let mut block = BeaconBlock::random_for_test(&mut rng);
block.parent_root = parent_hashes[i];
block.previous_block_root = parent_hashes[i];
block.slot = slots[i];
let ssz = ssz_encode(&block);
@@ -239,12 +223,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, reader) = bs
let (matched_block_hash, block) = bs
.block_at_slot(&hashes[hashes_index], slots[slot_index])
.unwrap()
.unwrap();
assert_eq!(matched_block_hash, hashes[slot_index]);
assert_eq!(reader.slot(), slots[slot_index]);
assert_eq!(block.slot, slots[slot_index]);
}
let ssz = bs.block_at_slot(&hashes[4], Slot::new(2)).unwrap();

View File

@@ -2,7 +2,7 @@ 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};
use types::{BeaconState, Hash256};
pub struct BeaconStateStore<T>
where
@@ -30,23 +30,6 @@ impl<T: ClientDB> BeaconStateStore<T> {
}
}
}
/// 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)]
@@ -72,8 +55,7 @@ mod tests {
store.put(&state_root, &ssz_encode(&state)).unwrap();
let reader = store.get_reader(&state_root).unwrap().unwrap();
let decoded = reader.into_beacon_state().unwrap();
let decoded = store.get_deserialized(&state_root).unwrap().unwrap();
assert_eq!(state, decoded);
}