Merge branch 'master' into disk-db

This commit is contained in:
Paul Hauner
2019-05-20 16:56:04 +10:00
203 changed files with 3893 additions and 19921 deletions

View File

@@ -5,7 +5,6 @@ authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com
edition = "2018"
[dependencies]
block_proposer = { path = "../../eth2/block_proposer" }
bls = { path = "../../eth2/utils/bls" }
boolean-bitfield = { path = "../../eth2/utils/boolean-bitfield" }
db = { path = "../db" }

View File

@@ -83,30 +83,31 @@ impl BlockProcessingOutcome {
}
}
pub struct BeaconChain<T: ClientDB + Sized, U: SlotClock, F: ForkChoice> {
pub struct BeaconChain<T: ClientDB + Sized, U: SlotClock, F: ForkChoice, E: EthSpec> {
pub block_store: Arc<BeaconBlockStore<T>>,
pub state_store: Arc<BeaconStateStore<T>>,
pub slot_clock: U,
pub op_pool: OperationPool,
canonical_head: RwLock<CheckPoint>,
finalized_head: RwLock<CheckPoint>,
pub state: RwLock<BeaconState>,
pub op_pool: OperationPool<E>,
canonical_head: RwLock<CheckPoint<E>>,
finalized_head: RwLock<CheckPoint<E>>,
pub state: RwLock<BeaconState<E>>,
pub spec: ChainSpec,
pub fork_choice: RwLock<F>,
}
impl<T, U, F> BeaconChain<T, U, F>
impl<T, U, F, E> BeaconChain<T, U, F, E>
where
T: ClientDB,
U: SlotClock,
F: ForkChoice,
E: EthSpec,
{
/// Instantiate a new Beacon Chain, from genesis.
pub fn from_genesis(
state_store: Arc<BeaconStateStore<T>>,
block_store: Arc<BeaconBlockStore<T>>,
slot_clock: U,
mut genesis_state: BeaconState,
mut genesis_state: BeaconState<E>,
genesis_block: BeaconBlock,
spec: ChainSpec,
fork_choice: F,
@@ -190,7 +191,6 @@ where
count: usize,
skip: usize,
) -> Result<Vec<Hash256>, Error> {
let spec = &self.spec;
let step_by = Slot::from(skip + 1);
let mut roots: Vec<Hash256> = vec![];
@@ -218,7 +218,7 @@ where
//
// If we get `SlotOutOfBounds` error, load the oldest available historic
// state from the DB.
match state.get_block_root(slot, spec) {
match state.get_block_root(slot) {
Ok(root) => {
if slot < earliest_slot {
break;
@@ -230,9 +230,9 @@ where
Err(BeaconStateError::SlotOutOfBounds) => {
// Read the earliest historic state in the current slot.
let earliest_historic_slot =
state.slot - Slot::from(spec.slots_per_historical_root);
state.slot - Slot::from(E::SlotsPerHistoricalRoot::to_usize());
// Load the earlier state from disk.
let new_state_root = state.get_state_root(earliest_historic_slot, spec)?;
let new_state_root = state.get_state_root(earliest_historic_slot)?;
// Break if the DB is unable to load the state.
state = match self.state_store.get_deserialized(&new_state_root) {
@@ -270,7 +270,7 @@ where
&self,
new_beacon_block: BeaconBlock,
new_beacon_block_root: Hash256,
new_beacon_state: BeaconState,
new_beacon_state: BeaconState<E>,
new_beacon_state_root: Hash256,
) {
debug!(
@@ -292,7 +292,7 @@ where
/// It is important to note that the `beacon_state` returned may not match the present slot. It
/// is the state as it was when the head block was received, which could be some slots prior to
/// now.
pub fn head(&self) -> RwLockReadGuard<CheckPoint> {
pub fn head(&self) -> RwLockReadGuard<CheckPoint<E>> {
self.canonical_head.read()
}
@@ -302,7 +302,7 @@ where
/// state and calling `catchup_state` as it will not result in an old state being installed and
/// then having it iteratively updated -- in such a case it's possible for another thread to
/// find the state at an old slot.
pub fn update_state(&self, mut state: BeaconState) -> Result<(), Error> {
pub fn update_state(&self, mut state: BeaconState<E>) -> Result<(), Error> {
let present_slot = match self.slot_clock.present_slot() {
Ok(Some(slot)) => slot,
_ => return Err(Error::UnableToReadSlot),
@@ -357,7 +357,7 @@ where
&self,
new_beacon_block: BeaconBlock,
new_beacon_block_root: Hash256,
new_beacon_state: BeaconState,
new_beacon_state: BeaconState<E>,
new_beacon_state_root: Hash256,
) {
let mut finalized_head = self.finalized_head.write();
@@ -371,7 +371,7 @@ where
/// Returns a read-lock guarded `CheckPoint` struct for reading the justified head (as chosen,
/// indirectly, by the fork-choice rule).
pub fn finalized_head(&self) -> RwLockReadGuard<CheckPoint> {
pub fn finalized_head(&self) -> RwLockReadGuard<CheckPoint<E>> {
self.finalized_head.read()
}
@@ -493,17 +493,14 @@ where
} else {
// If the current head block is not from this slot, use the slot from the previous
// epoch.
*self.state.read().get_block_root(
current_epoch_start_slot - self.spec.slots_per_epoch,
&self.spec,
)?
*self
.state
.read()
.get_block_root(current_epoch_start_slot - self.spec.slots_per_epoch)?
}
} else {
// If we're not on the first slot of the epoch.
*self
.state
.read()
.get_block_root(current_epoch_start_slot, &self.spec)?
*self.state.read().get_block_root(current_epoch_start_slot)?
};
Ok(AttestationData {
@@ -667,7 +664,7 @@ where
pub fn produce_block(
&self,
randao_reveal: Signature,
) -> Result<(BeaconBlock, BeaconState), BlockProductionError> {
) -> Result<(BeaconBlock, BeaconState<E>), BlockProductionError> {
debug!("Producing block at slot {}...", self.state.read().slot);
let mut state = self.state.read().clone();
@@ -677,7 +674,7 @@ where
trace!("Finding attestations for new block...");
let previous_block_root = *state
.get_block_root(state.slot - 1, &self.spec)
.get_block_root(state.slot - 1)
.map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?;
let (proposer_slashings, attester_slashings) =
@@ -762,7 +759,7 @@ where
///
/// 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> {
pub fn chain_dump(&self) -> Result<Vec<CheckPoint<E>>, Error> {
let mut dump = vec![];
let mut last_slot = CheckPoint {

View File

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

View File

@@ -11,14 +11,21 @@ use std::path::PathBuf;
use std::sync::Arc;
use tree_hash::TreeHash;
use types::test_utils::TestingBeaconStateBuilder;
use types::{BeaconBlock, ChainSpec, Hash256};
use types::{BeaconBlock, ChainSpec, FewValidatorsEthSpec, FoundationEthSpec, Hash256};
//TODO: Correct this for prod
//TODO: Account for historical db
pub fn initialise_beacon_chain(
spec: &ChainSpec,
db_name: Option<&PathBuf>,
) -> Arc<BeaconChain<DiskDB, SystemTimeSlotClock, BitwiseLMDGhost<DiskDB>>> {
) -> Arc<
BeaconChain<
DiskDB,
SystemTimeSlotClock,
BitwiseLMDGhost<DiskDB, FoundationEthSpec>,
FoundationEthSpec,
>,
> {
// set up the db
let db = Arc::new(DiskDB::open(
db_name.expect("Database directory must be included"),
@@ -64,7 +71,14 @@ pub fn initialise_beacon_chain(
pub fn initialise_test_beacon_chain_with_memory_db(
spec: &ChainSpec,
_db_name: Option<&PathBuf>,
) -> Arc<BeaconChain<MemoryDB, SystemTimeSlotClock, BitwiseLMDGhost<MemoryDB>>> {
) -> Arc<
BeaconChain<
MemoryDB,
SystemTimeSlotClock,
BitwiseLMDGhost<MemoryDB, FewValidatorsEthSpec>,
FewValidatorsEthSpec,
>,
> {
let db = Arc::new(MemoryDB::open());
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
let state_store = Arc::new(BeaconStateStore::new(db.clone()));

View File

@@ -7,17 +7,18 @@ use fork_choice::BitwiseLMDGhost;
use slot_clock::TestingSlotClock;
use std::sync::Arc;
use tree_hash::TreeHash;
use types::test_utils::TestingBeaconStateBuilder;
use types::*;
use types::{test_utils::TestingBeaconStateBuilder, EthSpec, FewValidatorsEthSpec};
type TestingBeaconChain = BeaconChain<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>;
type TestingBeaconChain<E> =
BeaconChain<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB, FewValidatorsEthSpec>, E>;
pub struct TestingBeaconChainBuilder {
state_builder: TestingBeaconStateBuilder,
pub struct TestingBeaconChainBuilder<E: EthSpec> {
state_builder: TestingBeaconStateBuilder<E>,
}
impl TestingBeaconChainBuilder {
pub fn build(self, spec: &ChainSpec) -> TestingBeaconChain {
impl<E: EthSpec> TestingBeaconChainBuilder<E> {
pub fn build(self, spec: &ChainSpec) -> TestingBeaconChain<E> {
let db = Arc::new(MemoryDB::open());
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
let state_store = Arc::new(BeaconStateStore::new(db.clone()));
@@ -43,8 +44,8 @@ impl TestingBeaconChainBuilder {
}
}
impl From<TestingBeaconStateBuilder> for TestingBeaconChainBuilder {
fn from(state_builder: TestingBeaconStateBuilder) -> TestingBeaconChainBuilder {
impl<E: EthSpec> From<TestingBeaconStateBuilder<E>> for TestingBeaconChainBuilder<E> {
fn from(state_builder: TestingBeaconStateBuilder<E>) -> TestingBeaconChainBuilder<E> {
TestingBeaconChainBuilder { state_builder }
}
}

View File

@@ -1,43 +0,0 @@
[package]
name = "test_harness"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[[bin]]
name = "test_harness"
path = "src/bin.rs"
[lib]
name = "test_harness"
path = "src/lib.rs"
[dev-dependencies]
state_processing = { path = "../../../eth2/state_processing" }
[dependencies]
attester = { path = "../../../eth2/attester" }
beacon_chain = { path = "../../beacon_chain" }
block_proposer = { path = "../../../eth2/block_proposer" }
bls = { path = "../../../eth2/utils/bls" }
boolean-bitfield = { path = "../../../eth2/utils/boolean-bitfield" }
clap = "2.32.0"
db = { path = "../../db" }
parking_lot = "0.7"
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" }
tree_hash = { path = "../../../eth2/utils/tree_hash" }
types = { path = "../../../eth2/types" }
yaml-rust = "0.4.2"

View File

@@ -1,150 +0,0 @@
# Test Harness
Provides a testing environment for the `BeaconChain`, `Attester` and `BlockProposer` objects.
This environment bypasses networking and client run-times and connects the `Attester` and `Proposer`
directly to the `BeaconChain` via an `Arc`.
The `BeaconChainHarness` contains a single `BeaconChain` instance and many `ValidatorHarness`
instances. All of the `ValidatorHarness` instances work to advance the `BeaconChain` by
producing blocks and attestations.
The crate consists of a library and binary, examples for using both are
described below.
## YAML
Both the library and the binary are capable of parsing tests from a YAML file,
in fact this is the sole purpose of the binary.
You can find YAML test cases [here](specs/). An example is included below:
```yaml
title: Validator Registry Tests
summary: Tests deposit and slashing effects on validator registry.
test_suite: validator_registry
fork: tchaikovsky
version: 1.0
test_cases:
- config:
slots_per_epoch: 64
deposits_for_chain_start: 1000
num_slots: 64
skip_slots: [2, 3]
deposits:
# At slot 1, create a new validator deposit of 32 ETH.
- slot: 1
amount: 32
# Trigger more deposits...
- slot: 3
amount: 32
- slot: 5
amount: 32
proposer_slashings:
# At slot 2, trigger a proposer slashing for validator #42.
- slot: 2
validator_index: 42
# Trigger another slashing...
- slot: 8
validator_index: 13
attester_slashings:
# At slot 2, trigger an attester slashing for validators #11 and #12.
- slot: 2
validator_indices: [11, 12]
# Trigger another slashing...
- slot: 5
validator_indices: [14]
results:
num_skipped_slots: 2
states:
- slot: 63
num_validators: 1003
slashed_validators: [11, 12, 13, 14, 42]
exited_validators: []
```
Thanks to [prsym](http://github.com/prysmaticlabs/prysm) for coming up with the
base YAML format.
### Notes
Wherever `slot` is used, it is actually the "slot height", or slots since
genesis. This allows the tests to disregard the `GENESIS_EPOCH`.
### Differences from Prysmatic's format
1. The detail for `deposits`, `proposer_slashings` and `attester_slashings` is
ommitted from the test specification. It assumed they should be valid
objects.
2. There is a `states` list in `results` that runs checks against any state
specified by a `slot` number. This is in contrast to the variables in
`results` that assume the last (highest) state should be inspected.
#### Reasoning
Respective reasonings for above changes:
1. This removes the concerns of the actual object structure from the tests.
This allows for more variation in the deposits/slashings objects without
needing to update the tests. Also, it makes it makes it easier to create
tests.
2. This gives more fine-grained control over the tests. It allows for checking
that certain events happened at certain times whilst making the tests only
slightly more verbose.
_Notes: it may be useful to add an extra field to each slashing type to
indicate if it should be valid or not. It also may be useful to add an option
for double-vote/surround-vote attester slashings. The `amount` field was left
on `deposits` as it changes the behaviour of state significantly._
## Binary Usage Example
Follow these steps to run as a binary:
1. Navigate to the root of this crate (where this readme is located)
2. Run `$ cargo run --release -- --yaml examples/validator_registry.yaml`
_Note: the `--release` flag builds the binary without all the debugging
instrumentation. The test is much faster built using `--release`. As is
customary in cargo, the flags before `--` are passed to cargo and the flags
after are passed to the binary._
### CLI Options
```
Lighthouse Test Harness Runner 0.0.1
Sigma Prime <contact@sigmaprime.io>
Runs `test_harness` using a YAML test_case.
USAGE:
test_harness --log-level <LOG_LEVEL> --yaml <FILE>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--log-level <LOG_LEVEL> Logging level. [default: debug] [possible values: error, warn, info, debug, trace]
--yaml <FILE> YAML file test_case.
```
## Library Usage Example
```rust
use test_harness::BeaconChainHarness;
use types::ChainSpec;
let validator_count = 8;
let spec = ChainSpec::few_validators();
let mut harness = BeaconChainHarness::new(spec, validator_count);
harness.advance_chain_with_block();
let chain = harness.chain_dump().unwrap();
// One block should have been built on top of the genesis block.
assert_eq!(chain.len(), 2);
```

View File

@@ -1,63 +0,0 @@
title: Validator Registry Tests
summary: Tests deposit and slashing effects on validator registry.
test_suite: validator_registry
fork: tchaikovsky
version: 1.0
test_cases:
- config:
slots_per_epoch: 64
deposits_for_chain_start: 1000
num_slots: 64
skip_slots: [2, 3]
persistent_committee_period: 0
deposits:
# At slot 1, create a new validator deposit of 5 ETH.
- slot: 1
amount: 5000000000
# Trigger more deposits...
- slot: 3
amount: 5000000000
- slot: 5
amount: 32000000000
exits:
# At slot 10, submit an exit for validator #50.
- slot: 10
validator_index: 50
transfers:
- slot: 6
from: 1000
to: 1001
amount: 5000000000
proposer_slashings:
# At slot 2, trigger a proposer slashing for validator #42.
- slot: 2
validator_index: 42
# Trigger another slashing...
- slot: 8
validator_index: 13
attester_slashings:
# At slot 2, trigger an attester slashing for validators #11 and #12.
- slot: 2
validator_indices: [11, 12]
# Trigger another slashing...
- slot: 5
validator_indices: [14]
results:
num_skipped_slots: 2
states:
- slot: 63
num_validators: 1003
num_previous_epoch_attestations: 0
# slots_per_epoch - attestation_inclusion_delay - skip_slots
num_current_epoch_attestations: 57
slashed_validators: [11, 12, 13, 14, 42]
exited_validators: []
exit_initiated_validators: [50]
balances:
- validator_index: 1000
comparison: "eq"
balance: 0
- validator_index: 1001
comparison: "eq"
balance: 10000000000

View File

@@ -1,350 +0,0 @@
use super::ValidatorHarness;
use beacon_chain::{BeaconChain, BlockProcessingOutcome};
pub use beacon_chain::{BeaconChainError, CheckPoint};
use db::{
stores::{BeaconBlockStore, BeaconStateStore},
MemoryDB,
};
use fork_choice::BitwiseLMDGhost;
use log::debug;
use rayon::prelude::*;
use slot_clock::TestingSlotClock;
use std::sync::Arc;
use tree_hash::TreeHash;
use types::{test_utils::TestingBeaconStateBuilder, *};
type TestingBeaconChain = BeaconChain<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>;
/// 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
/// information and submit blocks/attestations for processing.
///
/// This test harness is useful for testing validator and internal state transition logic. It
/// is not useful for testing that multiple beacon nodes can reach consensus.
pub struct BeaconChainHarness {
pub db: Arc<MemoryDB>,
pub beacon_chain: Arc<TestingBeaconChain>,
pub block_store: Arc<BeaconBlockStore<MemoryDB>>,
pub state_store: Arc<BeaconStateStore<MemoryDB>>,
pub validators: Vec<ValidatorHarness>,
pub spec: Arc<ChainSpec>,
}
impl BeaconChainHarness {
/// Create a new harness with:
///
/// - A keypair, `BlockProducer` and `Attester` for each validator.
/// - A new BeaconChain struct where the given validators are in the genesis.
pub fn new(spec: ChainSpec, validator_count: usize) -> Self {
let state_builder =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
Self::from_beacon_state_builder(state_builder, spec)
}
pub fn from_beacon_state_builder(
state_builder: TestingBeaconStateBuilder,
spec: ChainSpec,
) -> Self {
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 slot_clock = TestingSlotClock::new(spec.genesis_slot.as_u64());
let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone());
let (mut genesis_state, keypairs) = state_builder.build();
let mut genesis_block = BeaconBlock::empty(&spec);
genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root());
genesis_state
.build_epoch_cache(RelativeEpoch::Previous, &spec)
.unwrap();
genesis_state
.build_epoch_cache(RelativeEpoch::Current, &spec)
.unwrap();
genesis_state
.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)
.unwrap();
genesis_state
.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)
.unwrap();
// Create the Beacon Chain
let beacon_chain = Arc::new(
BeaconChain::from_genesis(
state_store.clone(),
block_store.clone(),
slot_clock,
genesis_state,
genesis_block,
spec.clone(),
fork_choice,
)
.unwrap(),
);
let spec = Arc::new(spec);
debug!("Creating validator producer and attester instances...");
// Spawn the test validator instances.
let validators: Vec<ValidatorHarness> = keypairs
.iter()
.map(|keypair| {
ValidatorHarness::new(keypair.clone(), beacon_chain.clone(), spec.clone())
})
.collect();
debug!("Created {} ValidatorHarnesss", validators.len());
Self {
db,
beacon_chain,
block_store,
state_store,
validators,
spec,
}
}
/// Move the `slot_clock` for the `BeaconChain` forward one slot.
///
/// This is the equivalent of advancing a system clock forward one `SLOT_DURATION`.
///
/// Returns the new slot.
pub fn increment_beacon_chain_slot(&mut self) -> Slot {
let slot = self.beacon_chain.present_slot() + 1;
let nth_slot = slot
- slot
.epoch(self.spec.slots_per_epoch)
.start_slot(self.spec.slots_per_epoch);
let nth_epoch = slot.epoch(self.spec.slots_per_epoch) - self.spec.genesis_epoch;
debug!(
"Advancing BeaconChain to slot {}, epoch {} (epoch height: {}, slot {} in epoch.).",
slot,
slot.epoch(self.spec.slots_per_epoch),
nth_epoch,
nth_slot
);
self.beacon_chain.slot_clock.set_slot(slot.as_u64());
self.beacon_chain
.catchup_state()
.expect("Failed to catch state");
slot
}
pub fn gather_attesations(&mut self) -> Vec<Attestation> {
let present_slot = self.beacon_chain.present_slot();
let state = self.beacon_chain.state.read();
let mut attestations = vec![];
for committee in state
.get_crosslink_committees_at_slot(present_slot, &self.spec)
.unwrap()
{
for &validator in &committee.committee {
let duties = state
.get_attestation_duties(validator, &self.spec)
.unwrap()
.expect("Attesting validators by definition have duties");
// Obtain `AttestationData` from the beacon chain.
let data = self
.beacon_chain
.produce_attestation_data(duties.shard)
.unwrap();
// Produce an aggregate signature with a single signature.
let aggregate_signature = {
let message = AttestationDataAndCustodyBit {
data: data.clone(),
custody_bit: false,
}
.tree_hash_root();
let domain = self.spec.get_domain(
state.slot.epoch(self.spec.slots_per_epoch),
Domain::Attestation,
&state.fork,
);
let sig =
Signature::new(&message, domain, &self.validators[validator].keypair.sk);
let mut agg_sig = AggregateSignature::new();
agg_sig.add(&sig);
agg_sig
};
let mut aggregation_bitfield = Bitfield::with_capacity(duties.committee_len);
let custody_bitfield = Bitfield::with_capacity(duties.committee_len);
aggregation_bitfield.set(duties.committee_index, true);
attestations.push(Attestation {
aggregation_bitfield,
data,
custody_bitfield,
aggregate_signature,
})
}
}
attestations
}
/// Get the block from the proposer for the slot.
///
/// Note: the validator will only produce it _once per slot_. So, if you call this twice you'll
/// only get a block once.
pub fn produce_block(&mut self) -> BeaconBlock {
let present_slot = self.beacon_chain.present_slot();
let proposer = self.beacon_chain.block_proposer(present_slot).unwrap();
debug!(
"Producing block from validator #{} for slot {}.",
proposer, present_slot
);
// Ensure the validators slot clock is accurate.
self.validators[proposer].set_slot(present_slot);
self.validators[proposer].produce_block().unwrap()
}
/// Advances the chain with a BeaconBlock and attestations from all validators.
///
/// This is the ideal scenario for the Beacon Chain, 100% honest participation from
/// validators.
pub fn advance_chain_with_block(&mut self) -> BeaconBlock {
self.increment_beacon_chain_slot();
// Produce a new block.
let block = self.produce_block();
debug!("Submitting block for processing...");
match self.beacon_chain.process_block(block.clone()) {
Ok(BlockProcessingOutcome::ValidBlock(_)) => {}
other => panic!("block processing failed with {:?}", other),
};
debug!("...block processed by BeaconChain.");
debug!("Producing attestations...");
// Produce new attestations.
let attestations = self.gather_attesations();
debug!("Processing {} attestations...", attestations.len());
attestations
.par_iter()
.enumerate()
.for_each(|(i, attestation)| {
self.beacon_chain
.process_attestation(attestation.clone())
.unwrap_or_else(|_| panic!("Attestation {} invalid: {:?}", i, attestation));
});
debug!("Attestations processed.");
block
}
/// Signs a message using some validators secret key with the `Fork` info from the latest state
/// of the `BeaconChain`.
///
/// Useful for producing slashable messages and other objects that `BeaconChainHarness` does
/// not produce naturally.
pub fn validator_sign(
&self,
validator_index: usize,
message: &[u8],
epoch: Epoch,
domain_type: Domain,
) -> Option<Signature> {
let validator = self.validators.get(validator_index)?;
let domain = self
.spec
.get_domain(epoch, domain_type, &self.beacon_chain.state.read().fork);
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.
///
/// If a new `ValidatorHarness` was created, the validator should become fully operational as
/// if the validator were created during `BeaconChainHarness` instantiation.
pub fn add_deposit(&mut self, deposit: Deposit, keypair: Option<Keypair>) {
self.beacon_chain.process_deposit(deposit).unwrap();
// If a keypair is present, add a new `ValidatorHarness` to the rig.
if let Some(keypair) = keypair {
let validator =
ValidatorHarness::new(keypair, self.beacon_chain.clone(), self.spec.clone());
self.validators.push(validator);
}
}
/// Submit an exit to the `BeaconChain` for inclusion in some block.
///
/// Note: the `ValidatorHarness` for this validator continues to exist. Once it is exited it
/// will stop receiving duties from the beacon chain and just do nothing when prompted to
/// produce/attest.
pub fn add_exit(&mut self, exit: VoluntaryExit) {
self.beacon_chain.process_voluntary_exit(exit).unwrap();
}
/// Submit an transfer to the `BeaconChain` for inclusion in some block.
pub fn add_transfer(&mut self, transfer: Transfer) {
self.beacon_chain.process_transfer(transfer).unwrap();
}
/// Submit a proposer slashing to the `BeaconChain` for inclusion in some block.
pub fn add_proposer_slashing(&mut self, proposer_slashing: ProposerSlashing) {
self.beacon_chain
.process_proposer_slashing(proposer_slashing)
.unwrap();
}
/// Submit an attester slashing to the `BeaconChain` for inclusion in some block.
pub fn add_attester_slashing(&mut self, attester_slashing: AttesterSlashing) {
self.beacon_chain
.process_attester_slashing(attester_slashing)
.unwrap();
}
/// Executes the fork choice rule on the `BeaconChain`, selecting a new canonical head.
pub fn run_fork_choice(&mut self) {
self.beacon_chain.fork_choice().unwrap()
}
/// Dump all blocks and states from the canonical beacon chain.
pub fn chain_dump(&self) -> Result<Vec<CheckPoint>, BeaconChainError> {
self.beacon_chain.chain_dump()
}
}

View File

@@ -1,102 +0,0 @@
use clap::{App, Arg, SubCommand};
use env_logger::{Builder, Env};
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("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();
}
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.
};
if let Some(matches) = matches.subcommand_matches("run_test") {
run_test(matches);
}
if let Some(matches) = matches.subcommand_matches("gen_keys") {
gen_keys(matches);
}
}

View File

@@ -1,21 +0,0 @@
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

@@ -1,33 +0,0 @@
//! Provides a testing environment for the `BeaconChain`, `Attester` and `BlockProposer` objects.
//!
//! This environment bypasses networking and client run-times and connects the `Attester` and `Proposer`
//! directly to the `BeaconChain` via an `Arc`.
//!
//! The `BeaconChainHarness` contains a single `BeaconChain` instance and many `ValidatorHarness`
//! instances. All of the `ValidatorHarness` instances work to advance the `BeaconChain` by
//! producing blocks and attestations.
//!
//! Example:
//! ```rust,no_run
//! use test_harness::BeaconChainHarness;
//! use types::ChainSpec;
//!
//! let validator_count = 8;
//! let spec = ChainSpec::few_validators();
//!
//! let mut harness = BeaconChainHarness::new(spec, validator_count);
//!
//! harness.advance_chain_with_block();
//!
//! let chain = harness.chain_dump().unwrap();
//!
//! // One block should have been built on top of the genesis block.
//! assert_eq!(chain.len(), 2);
//! ```
mod beacon_chain_harness;
pub mod test_case;
mod validator_harness;
pub use self::beacon_chain_harness::BeaconChainHarness;
pub use self::validator_harness::ValidatorHarness;

View File

@@ -1,37 +0,0 @@
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

@@ -1,312 +0,0 @@
//! Defines execution and testing specs for a `BeaconChainHarness` instance. Supports loading from
//! a YAML file.
use crate::beacon_chain_harness::BeaconChainHarness;
use beacon_chain::CheckPoint;
use log::{info, warn};
use tree_hash::SignedRoot;
use types::*;
use types::test_utils::*;
use yaml_rust::Yaml;
mod config;
mod results;
mod state_check;
mod yaml_helpers;
pub use config::Config;
pub use results::Results;
pub use state_check::StateCheck;
/// Defines the execution and testing of a `BeaconChainHarness` instantiation.
///
/// Typical workflow is:
///
/// 1. Instantiate the `TestCase` from YAML: `let test_case = TestCase::from_yaml(&my_yaml);`
/// 2. Execute the test_case: `let result = test_case.execute();`
/// 3. Test the results against the test_case: `test_case.assert_result_valid(result);`
#[derive(Debug)]
pub struct TestCase {
/// Defines the execution.
pub config: Config,
/// Defines tests to run against the execution result.
pub results: Results,
}
/// The result of executing a `TestCase`.
///
pub struct ExecutionResult {
/// The canonical beacon chain generated from the execution.
pub chain: Vec<CheckPoint>,
/// The spec used for execution.
pub spec: ChainSpec,
}
impl TestCase {
/// Load the test case from a YAML document.
pub fn from_yaml(test_case: &Yaml) -> Self {
Self {
results: Results::from_yaml(&test_case["results"]),
config: Config::from_yaml(&test_case["config"]),
}
}
/// Return a `ChainSpec::foundation()`.
///
/// If specified in `config`, returns it with a modified `slots_per_epoch`.
fn spec(&self) -> ChainSpec {
let mut spec = ChainSpec::foundation();
if let Some(n) = self.config.slots_per_epoch {
spec.slots_per_epoch = n;
}
if let Some(n) = self.config.persistent_committee_period {
spec.persistent_committee_period = n;
}
spec
}
/// Executes the test case, returning an `ExecutionResult`.
#[allow(clippy::cyclomatic_complexity)]
pub fn execute(&self) -> ExecutionResult {
let spec = self.spec();
let validator_count = self.config.deposits_for_chain_start;
let slots = self.config.num_slots;
info!(
"Building BeaconChainHarness with {} validators...",
validator_count
);
let mut harness = BeaconChainHarness::new(spec, validator_count);
info!("Starting simulation across {} slots...", slots);
// Start at 1 because genesis counts as a slot.
for slot_height in 1..slots {
// Used to ensure that deposits in the same slot have incremental deposit indices.
let mut deposit_index_offset = 0;
// Feed deposits to the BeaconChain.
if let Some(ref deposits) = self.config.deposits {
for (slot, amount) in deposits {
if *slot == slot_height {
info!("Including deposit at slot height {}.", slot_height);
let (deposit, keypair) =
build_deposit(&harness, *amount, deposit_index_offset);
harness.add_deposit(deposit, Some(keypair.clone()));
deposit_index_offset += 1;
}
}
}
// Feed proposer slashings to the BeaconChain.
if let Some(ref slashings) = self.config.proposer_slashings {
for (slot, validator_index) in slashings {
if *slot == slot_height {
info!(
"Including proposer slashing at slot height {} for validator #{}.",
slot_height, validator_index
);
let slashing = build_proposer_slashing(&harness, *validator_index);
harness.add_proposer_slashing(slashing);
}
}
}
// Feed attester slashings to the BeaconChain.
if let Some(ref slashings) = self.config.attester_slashings {
for (slot, validator_indices) in slashings {
if *slot == slot_height {
info!(
"Including attester slashing at slot height {} for validators {:?}.",
slot_height, validator_indices
);
let slashing =
build_double_vote_attester_slashing(&harness, &validator_indices[..]);
harness.add_attester_slashing(slashing);
}
}
}
// Feed exits to the BeaconChain.
if let Some(ref exits) = self.config.exits {
for (slot, validator_index) in exits {
if *slot == slot_height {
info!(
"Including exit at slot height {} for validator {}.",
slot_height, validator_index
);
let exit = build_exit(&harness, *validator_index);
harness.add_exit(exit);
}
}
}
// Feed transfers to the BeaconChain.
if let Some(ref transfers) = self.config.transfers {
for (slot, from, to, amount) in transfers {
if *slot == slot_height {
info!(
"Including transfer at slot height {} from validator {}.",
slot_height, from
);
let transfer = build_transfer(&harness, *from, *to, *amount);
harness.add_transfer(transfer);
}
}
}
// Build a block or skip a slot.
match self.config.skip_slots {
Some(ref skip_slots) if skip_slots.contains(&slot_height) => {
warn!("Skipping slot at height {}.", slot_height);
harness.increment_beacon_chain_slot();
}
_ => {
info!("Producing block at slot height {}.", slot_height);
harness.advance_chain_with_block();
}
}
}
harness.run_fork_choice();
info!("Test execution complete!");
info!("Building chain dump for analysis...");
ExecutionResult {
chain: harness.chain_dump().expect("Chain dump failed."),
spec: (*harness.spec).clone(),
}
}
/// Checks that the `ExecutionResult` is consistent with the specifications in `self.results`.
///
/// # Panics
///
/// Panics with a message if any result does not match exepectations.
pub fn assert_result_valid(&self, execution_result: ExecutionResult) {
info!("Verifying test results...");
let spec = &execution_result.spec;
if let Some(num_skipped_slots) = self.results.num_skipped_slots {
assert_eq!(
execution_result.chain.len(),
self.config.num_slots as usize - num_skipped_slots,
"actual skipped slots != expected."
);
info!(
"OK: Chain length is {} ({} skipped slots).",
execution_result.chain.len(),
num_skipped_slots
);
}
if let Some(ref state_checks) = self.results.state_checks {
for checkpoint in &execution_result.chain {
let state = &checkpoint.beacon_state;
for state_check in state_checks {
let adjusted_state_slot =
state.slot - spec.genesis_epoch.start_slot(spec.slots_per_epoch);
if state_check.slot == adjusted_state_slot {
state_check.assert_valid(state, spec);
}
}
}
}
}
}
/// Builds a `Deposit` this is valid for the given `BeaconChainHarness` at its next slot.
fn build_transfer(
harness: &BeaconChainHarness,
sender: u64,
recipient: u64,
amount: u64,
) -> Transfer {
let slot = harness.beacon_chain.state.read().slot + 1;
let mut builder = TestingTransferBuilder::new(sender, recipient, amount, slot);
let keypair = harness.validator_keypair(sender as usize).unwrap();
builder.sign(keypair.clone(), &harness.fork(), &harness.spec);
builder.build()
}
/// Builds a `Deposit` this is valid for the given `BeaconChainHarness`.
///
/// `index_offset` is used to ensure that `deposit.index == state.index` when adding multiple
/// deposits.
fn build_deposit(
harness: &BeaconChainHarness,
amount: u64,
index_offset: u64,
) -> (Deposit, Keypair) {
let keypair = Keypair::random();
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);
(builder.build(), keypair)
}
/// Builds a `VoluntaryExit` this is valid for the given `BeaconChainHarness`.
fn build_exit(harness: &BeaconChainHarness, validator_index: u64) -> VoluntaryExit {
let epoch = harness
.beacon_chain
.state
.read()
.current_epoch(&harness.spec);
let mut exit = VoluntaryExit {
epoch,
validator_index,
signature: Signature::empty_signature(),
};
let message = exit.signed_root();
exit.signature = harness
.validator_sign(validator_index as usize, &message[..], epoch, Domain::Exit)
.expect("Unable to sign VoluntaryExit");
exit
}
/// Builds an `AttesterSlashing` for some `validator_indices`.
///
/// Signs the message using a `BeaconChainHarness`.
fn build_double_vote_attester_slashing(
harness: &BeaconChainHarness,
validator_indices: &[u64],
) -> AttesterSlashing {
let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: Domain| {
harness
.validator_sign(validator_index as usize, message, epoch, domain)
.expect("Unable to sign AttesterSlashing")
};
TestingAttesterSlashingBuilder::double_vote(validator_indices, signer)
}
/// Builds an `ProposerSlashing` for some `validator_index`.
///
/// Signs the message using a `BeaconChainHarness`.
fn build_proposer_slashing(harness: &BeaconChainHarness, validator_index: u64) -> ProposerSlashing {
let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: Domain| {
harness
.validator_sign(validator_index as usize, message, epoch, domain)
.expect("Unable to sign AttesterSlashing")
};
TestingProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec)
}

View File

@@ -1,135 +0,0 @@
use super::yaml_helpers::{as_u64, as_usize, as_vec_u64};
use types::*;
use yaml_rust::Yaml;
pub type ValidatorIndex = u64;
pub type ValidatorIndices = Vec<u64>;
pub type GweiAmount = u64;
pub type DepositTuple = (SlotHeight, GweiAmount);
pub type ExitTuple = (SlotHeight, ValidatorIndex);
pub type ProposerSlashingTuple = (SlotHeight, ValidatorIndex);
pub type AttesterSlashingTuple = (SlotHeight, ValidatorIndices);
/// (slot_height, from, to, amount)
pub type TransferTuple = (SlotHeight, ValidatorIndex, ValidatorIndex, GweiAmount);
/// Defines the execution of a `BeaconStateHarness` across a series of slots.
#[derive(Debug)]
pub struct Config {
/// Initial validators.
pub deposits_for_chain_start: usize,
/// Number of slots in an epoch.
pub slots_per_epoch: Option<u64>,
/// Affects the number of epochs a validator must be active before they can withdraw.
pub persistent_committee_period: Option<u64>,
/// Number of slots to build before ending execution.
pub num_slots: u64,
/// Number of slots that should be skipped due to inactive validator.
pub skip_slots: Option<Vec<u64>>,
/// Deposits to be included during execution.
pub deposits: Option<Vec<DepositTuple>>,
/// Proposer slashings to be included during execution.
pub proposer_slashings: Option<Vec<ProposerSlashingTuple>>,
/// Attester slashings to be including during execution.
pub attester_slashings: Option<Vec<AttesterSlashingTuple>>,
/// Exits to be including during execution.
pub exits: Option<Vec<ExitTuple>>,
/// Transfers to be including during execution.
pub transfers: Option<Vec<TransferTuple>>,
}
impl Config {
/// Load from a YAML document.
///
/// Expects to receive the `config` section of the document.
pub fn from_yaml(yaml: &Yaml) -> Self {
Self {
deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start")
.expect("Must specify validator count"),
slots_per_epoch: as_u64(&yaml, "slots_per_epoch"),
persistent_committee_period: as_u64(&yaml, "persistent_committee_period"),
num_slots: as_u64(&yaml, "num_slots").expect("Must specify `config.num_slots`"),
skip_slots: as_vec_u64(yaml, "skip_slots"),
deposits: parse_deposits(&yaml),
proposer_slashings: parse_proposer_slashings(&yaml),
attester_slashings: parse_attester_slashings(&yaml),
exits: parse_exits(&yaml),
transfers: parse_transfers(&yaml),
}
}
}
/// Parse the `transfers` section of the YAML document.
fn parse_transfers(yaml: &Yaml) -> Option<Vec<TransferTuple>> {
let mut tuples = vec![];
for exit in yaml["transfers"].as_vec()? {
let slot = as_u64(exit, "slot").expect("Incomplete transfer (slot)");
let from = as_u64(exit, "from").expect("Incomplete transfer (from)");
let to = as_u64(exit, "to").expect("Incomplete transfer (to)");
let amount = as_u64(exit, "amount").expect("Incomplete transfer (amount)");
tuples.push((SlotHeight::from(slot), from, to, amount));
}
Some(tuples)
}
/// Parse the `attester_slashings` section of the YAML document.
fn parse_exits(yaml: &Yaml) -> Option<Vec<ExitTuple>> {
let mut tuples = vec![];
for exit in yaml["exits"].as_vec()? {
let slot = as_u64(exit, "slot").expect("Incomplete exit (slot)");
let validator_index =
as_u64(exit, "validator_index").expect("Incomplete exit (validator_index)");
tuples.push((SlotHeight::from(slot), validator_index));
}
Some(tuples)
}
/// Parse the `attester_slashings` section of the YAML document.
fn parse_attester_slashings(yaml: &Yaml) -> Option<Vec<AttesterSlashingTuple>> {
let mut slashings = vec![];
for slashing in yaml["attester_slashings"].as_vec()? {
let slot = as_u64(slashing, "slot").expect("Incomplete attester_slashing (slot)");
let validator_indices = as_vec_u64(slashing, "validator_indices")
.expect("Incomplete attester_slashing (validator_indices)");
slashings.push((SlotHeight::from(slot), validator_indices));
}
Some(slashings)
}
/// Parse the `proposer_slashings` section of the YAML document.
fn parse_proposer_slashings(yaml: &Yaml) -> Option<Vec<ProposerSlashingTuple>> {
let mut slashings = vec![];
for slashing in yaml["proposer_slashings"].as_vec()? {
let slot = as_u64(slashing, "slot").expect("Incomplete proposer slashing (slot)_");
let validator_index = as_u64(slashing, "validator_index")
.expect("Incomplete proposer slashing (validator_index)");
slashings.push((SlotHeight::from(slot), validator_index));
}
Some(slashings)
}
/// Parse the `deposits` section of the YAML document.
fn parse_deposits(yaml: &Yaml) -> Option<Vec<DepositTuple>> {
let mut deposits = vec![];
for deposit in yaml["deposits"].as_vec()? {
let slot = as_u64(deposit, "slot").expect("Incomplete deposit (slot)");
let amount = as_u64(deposit, "amount").expect("Incomplete deposit (amount)");
deposits.push((SlotHeight::from(slot), amount))
}
Some(deposits)
}

View File

@@ -1,34 +0,0 @@
use super::state_check::StateCheck;
use super::yaml_helpers::as_usize;
use yaml_rust::Yaml;
/// A series of tests to be carried out upon an `ExecutionResult`, returned from executing a
/// `TestCase`.
#[derive(Debug)]
pub struct Results {
pub num_skipped_slots: Option<usize>,
pub state_checks: Option<Vec<StateCheck>>,
}
impl Results {
/// Load from a YAML document.
///
/// Expects the `results` section of the YAML document.
pub fn from_yaml(yaml: &Yaml) -> Self {
Self {
num_skipped_slots: as_usize(yaml, "num_skipped_slots"),
state_checks: parse_state_checks(yaml),
}
}
}
/// Parse the `state_checks` section of the YAML document.
fn parse_state_checks(yaml: &Yaml) -> Option<Vec<StateCheck>> {
let mut states = vec![];
for state_yaml in yaml["states"].as_vec()? {
states.push(StateCheck::from_yaml(state_yaml));
}
Some(states)
}

View File

@@ -1,206 +0,0 @@
use super::yaml_helpers::{as_u64, as_usize, as_vec_u64};
use log::info;
use types::*;
use yaml_rust::Yaml;
type ValidatorIndex = u64;
type BalanceGwei = u64;
type BalanceCheckTuple = (ValidatorIndex, String, BalanceGwei);
/// Tests to be conducted upon a `BeaconState` object generated during the execution of a
/// `TestCase`.
#[derive(Debug)]
pub struct StateCheck {
/// Checked against `beacon_state.slot`.
pub slot: Slot,
/// Checked against `beacon_state.validator_registry.len()`.
pub num_validators: Option<usize>,
/// The number of pending attestations from the previous epoch that should be in the state.
pub num_previous_epoch_attestations: Option<usize>,
/// The number of pending attestations from the current epoch that should be in the state.
pub num_current_epoch_attestations: Option<usize>,
/// A list of validator indices which have been penalized. Must be in ascending order.
pub slashed_validators: Option<Vec<u64>>,
/// A list of validator indices which have been fully exited. Must be in ascending order.
pub exited_validators: Option<Vec<u64>>,
/// A list of validator indices which have had an exit initiated. Must be in ascending order.
pub exit_initiated_validators: Option<Vec<u64>>,
/// A list of balances to check.
pub balances: Option<Vec<BalanceCheckTuple>>,
}
impl StateCheck {
/// Load from a YAML document.
///
/// Expects the `state_check` section of the YAML document.
pub fn from_yaml(yaml: &Yaml) -> Self {
Self {
slot: Slot::from(as_u64(&yaml, "slot").expect("State must specify slot")),
num_validators: as_usize(&yaml, "num_validators"),
num_previous_epoch_attestations: as_usize(&yaml, "num_previous_epoch_attestations"),
num_current_epoch_attestations: as_usize(&yaml, "num_current_epoch_attestations"),
slashed_validators: as_vec_u64(&yaml, "slashed_validators"),
exited_validators: as_vec_u64(&yaml, "exited_validators"),
exit_initiated_validators: as_vec_u64(&yaml, "exit_initiated_validators"),
balances: parse_balances(&yaml),
}
}
/// Performs all checks against a `BeaconState`
///
/// # Panics
///
/// Panics with an error message if any test fails.
#[allow(clippy::cyclomatic_complexity)]
pub fn assert_valid(&self, state: &BeaconState, spec: &ChainSpec) {
let state_epoch = state.slot.epoch(spec.slots_per_epoch);
info!("Running state check for slot height {}.", self.slot);
// Check the state slot.
assert_eq!(
self.slot,
state.slot - spec.genesis_epoch.start_slot(spec.slots_per_epoch),
"State slot is invalid."
);
// Check the validator count
if let Some(num_validators) = self.num_validators {
assert_eq!(
state.validator_registry.len(),
num_validators,
"State validator count != expected."
);
info!("OK: num_validators = {}.", num_validators);
}
// Check the previous epoch attestations
if let Some(n) = self.num_previous_epoch_attestations {
assert_eq!(
state.previous_epoch_attestations.len(),
n,
"previous epoch attestations count != expected."
);
info!("OK: num_previous_epoch_attestations = {}.", n);
}
// Check the current epoch attestations
if let Some(n) = self.num_current_epoch_attestations {
assert_eq!(
state.current_epoch_attestations.len(),
n,
"current epoch attestations count != expected."
);
info!("OK: num_current_epoch_attestations = {}.", n);
}
// Check for slashed validators.
if let Some(ref slashed_validators) = self.slashed_validators {
let actually_slashed_validators: Vec<u64> = state
.validator_registry
.iter()
.enumerate()
.filter_map(|(i, validator)| {
if validator.slashed {
Some(i as u64)
} else {
None
}
})
.collect();
assert_eq!(
actually_slashed_validators, *slashed_validators,
"Slashed validators != expected."
);
info!("OK: slashed_validators = {:?}.", slashed_validators);
}
// Check for exited validators.
if let Some(ref exited_validators) = self.exited_validators {
let actually_exited_validators: Vec<u64> = state
.validator_registry
.iter()
.enumerate()
.filter_map(|(i, validator)| {
if validator.is_exited_at(state_epoch) {
Some(i as u64)
} else {
None
}
})
.collect();
assert_eq!(
actually_exited_validators, *exited_validators,
"Exited validators != expected."
);
info!("OK: exited_validators = {:?}.", exited_validators);
}
// Check for validators that have initiated exit.
if let Some(ref exit_initiated_validators) = self.exit_initiated_validators {
let actual: Vec<u64> = state
.validator_registry
.iter()
.enumerate()
.filter_map(|(i, validator)| {
if validator.initiated_exit {
Some(i as u64)
} else {
None
}
})
.collect();
assert_eq!(
actual, *exit_initiated_validators,
"Exit initiated validators != expected."
);
info!(
"OK: exit_initiated_validators = {:?}.",
exit_initiated_validators
);
}
// Check validator balances.
if let Some(ref balances) = self.balances {
for (index, comparison, expected) in balances {
let actual = *state
.validator_balances
.get(*index as usize)
.expect("Balance check specifies unknown validator");
let result = match comparison.as_ref() {
"eq" => actual == *expected,
_ => panic!("Unknown balance comparison (use `eq`)"),
};
assert!(
result,
format!(
"Validator balance for {}: {} !{} {}.",
index, actual, comparison, expected
)
);
info!("OK: validator balance for {:?}.", index);
}
}
}
}
/// Parse the `transfers` section of the YAML document.
fn parse_balances(yaml: &Yaml) -> Option<Vec<BalanceCheckTuple>> {
let mut tuples = vec![];
for exit in yaml["balances"].as_vec()? {
let from =
as_u64(exit, "validator_index").expect("Incomplete balance check (validator_index)");
let comparison = exit["comparison"]
.clone()
.into_string()
.expect("Incomplete balance check (amount)");
let balance = as_u64(exit, "balance").expect("Incomplete balance check (balance)");
tuples.push((from, comparison, balance));
}
Some(tuples)
}

View File

@@ -1,19 +0,0 @@
use yaml_rust::Yaml;
pub fn as_usize(yaml: &Yaml, key: &str) -> Option<usize> {
yaml[key].as_i64().and_then(|n| Some(n as usize))
}
pub fn as_u64(yaml: &Yaml, key: &str) -> Option<u64> {
yaml[key].as_i64().and_then(|n| Some(n as u64))
}
pub fn as_vec_u64(yaml: &Yaml, key: &str) -> Option<Vec<u64>> {
yaml[key].clone().into_vec().and_then(|vec| {
Some(
vec.iter()
.map(|item| item.as_i64().unwrap() as u64)
.collect(),
)
})
}

View File

@@ -1,100 +0,0 @@
use attester::{
BeaconNode as AttesterBeaconNode, BeaconNodeError as NodeError,
PublishOutcome as AttestationPublishOutcome,
};
use beacon_chain::BeaconChain;
use block_proposer::{
BeaconNode as BeaconBlockNode, BeaconNodeError as BeaconBlockNodeError,
PublishOutcome as BlockPublishOutcome,
};
use db::ClientDB;
use fork_choice::ForkChoice;
use parking_lot::RwLock;
use slot_clock::SlotClock;
use std::sync::Arc;
use types::{AttestationData, BeaconBlock, FreeAttestation, Signature, Slot};
/// Connect directly to a borrowed `BeaconChain` instance so an attester/producer can request/submit
/// blocks/attestations.
///
/// `BeaconBlock`s and `FreeAttestation`s are not actually published to the `BeaconChain`, instead
/// they are stored inside this struct. This is to allow one to benchmark the submission of the
/// block/attestation directly, or modify it before submission.
pub struct DirectBeaconNode<T: ClientDB, U: SlotClock, F: ForkChoice> {
beacon_chain: Arc<BeaconChain<T, U, F>>,
published_blocks: RwLock<Vec<BeaconBlock>>,
published_attestations: RwLock<Vec<FreeAttestation>>,
}
impl<T: ClientDB, U: SlotClock, F: ForkChoice> DirectBeaconNode<T, U, F> {
pub fn new(beacon_chain: Arc<BeaconChain<T, U, F>>) -> Self {
Self {
beacon_chain,
published_blocks: RwLock::new(vec![]),
published_attestations: RwLock::new(vec![]),
}
}
/// Get the last published block (if any).
pub fn last_published_block(&self) -> Option<BeaconBlock> {
Some(self.published_blocks.read().last()?.clone())
}
}
impl<T: ClientDB, U: SlotClock, F: ForkChoice> AttesterBeaconNode for DirectBeaconNode<T, U, F> {
fn produce_attestation_data(
&self,
_slot: Slot,
shard: u64,
) -> Result<Option<AttestationData>, NodeError> {
match self.beacon_chain.produce_attestation_data(shard) {
Ok(attestation_data) => Ok(Some(attestation_data)),
Err(e) => Err(NodeError::RemoteFailure(format!("{:?}", e))),
}
}
fn publish_attestation(
&self,
free_attestation: FreeAttestation,
) -> Result<AttestationPublishOutcome, NodeError> {
self.published_attestations.write().push(free_attestation);
Ok(AttestationPublishOutcome::ValidAttestation)
}
}
impl<T: ClientDB, U: SlotClock, F: ForkChoice> BeaconBlockNode for DirectBeaconNode<T, U, F> {
/// Requests a new `BeaconBlock from the `BeaconChain`.
fn produce_beacon_block(
&self,
slot: Slot,
randao_reveal: &Signature,
) -> Result<Option<BeaconBlock>, BeaconBlockNodeError> {
let (block, _state) = self
.beacon_chain
.produce_block(randao_reveal.clone())
.map_err(|e| {
BeaconBlockNodeError::RemoteFailure(format!("Did not produce block: {:?}", e))
})?;
if block.slot == slot {
Ok(Some(block))
} else {
Err(BeaconBlockNodeError::RemoteFailure(
"Unable to produce at non-current slot.".to_string(),
))
}
}
/// A block is not _actually_ published to the `BeaconChain`, instead it is stored in the
/// `published_block_vec` and a successful `ValidBlock` is returned to the caller.
///
/// The block may be retrieved and then applied to the `BeaconChain` manually, potentially in a
/// benchmarking scenario.
fn publish_beacon_block(
&self,
block: BeaconBlock,
) -> Result<BlockPublishOutcome, BeaconBlockNodeError> {
self.published_blocks.write().push(block);
Ok(BlockPublishOutcome::ValidBlock)
}
}

View File

@@ -1,74 +0,0 @@
use attester::{
DutiesReader as AttesterDutiesReader, DutiesReaderError as AttesterDutiesReaderError,
};
use beacon_chain::BeaconChain;
use block_proposer::{
DutiesReader as ProducerDutiesReader, DutiesReaderError as ProducerDutiesReaderError,
};
use db::ClientDB;
use fork_choice::ForkChoice;
use slot_clock::SlotClock;
use std::sync::Arc;
use types::{Fork, PublicKey, Slot};
/// Connects directly to a borrowed `BeaconChain` and reads attester/proposer duties directly from
/// it.
pub struct DirectDuties<T: ClientDB, U: SlotClock, F: ForkChoice> {
beacon_chain: Arc<BeaconChain<T, U, F>>,
pubkey: PublicKey,
}
impl<T: ClientDB, U: SlotClock, F: ForkChoice> DirectDuties<T, U, F> {
pub fn new(pubkey: PublicKey, beacon_chain: Arc<BeaconChain<T, U, F>>) -> Self {
Self {
beacon_chain,
pubkey,
}
}
}
impl<T: ClientDB, U: SlotClock, F: ForkChoice> ProducerDutiesReader for DirectDuties<T, U, F> {
fn is_block_production_slot(&self, slot: Slot) -> Result<bool, ProducerDutiesReaderError> {
let validator_index = self
.beacon_chain
.validator_index(&self.pubkey)
.ok_or_else(|| ProducerDutiesReaderError::UnknownValidator)?;
match self.beacon_chain.block_proposer(slot) {
Ok(proposer) if proposer == validator_index => Ok(true),
Ok(_) => Ok(false),
Err(_) => Err(ProducerDutiesReaderError::UnknownEpoch),
}
}
fn fork(&self) -> Result<Fork, ProducerDutiesReaderError> {
Ok(self.beacon_chain.state.read().fork.clone())
}
}
impl<T: ClientDB, U: SlotClock, F: ForkChoice> AttesterDutiesReader for DirectDuties<T, U, F> {
fn validator_index(&self) -> Option<u64> {
match self.beacon_chain.validator_index(&self.pubkey) {
Some(index) => Some(index as u64),
None => None,
}
}
fn attestation_shard(&self, slot: Slot) -> Result<Option<u64>, AttesterDutiesReaderError> {
if let Some(validator_index) = self.validator_index() {
match self
.beacon_chain
.validator_attestion_slot_and_shard(validator_index as usize)
{
Ok(Some((attest_slot, attest_shard))) if attest_slot == slot => {
Ok(Some(attest_shard))
}
Ok(Some(_)) => Ok(None),
Ok(None) => Err(AttesterDutiesReaderError::UnknownEpoch),
Err(_) => unreachable!("Error when getting validator attestation shard."),
}
} else {
Err(AttesterDutiesReaderError::UnknownValidator)
}
}
}

View File

@@ -1,36 +0,0 @@
use attester::Signer as AttesterSigner;
use block_proposer::Signer as BlockProposerSigner;
use types::{Keypair, Signature};
/// A test-only struct used to perform signing for a proposer or attester.
pub struct LocalSigner {
keypair: Keypair,
}
impl LocalSigner {
/// Produce a new TestSigner with signing enabled by default.
pub fn new(keypair: Keypair) -> Self {
Self { keypair }
}
/// Sign some message.
fn bls_sign(&self, message: &[u8], domain: u64) -> Option<Signature> {
Some(Signature::new(message, domain, &self.keypair.sk))
}
}
impl BlockProposerSigner for LocalSigner {
fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option<Signature> {
self.bls_sign(message, domain)
}
fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option<Signature> {
self.bls_sign(message, domain)
}
}
impl AttesterSigner for LocalSigner {
fn sign_attestation_message(&self, message: &[u8], domain: u64) -> Option<Signature> {
self.bls_sign(message, domain)
}
}

View File

@@ -1,119 +0,0 @@
mod direct_beacon_node;
mod direct_duties;
mod local_signer;
use attester::Attester;
use beacon_chain::BeaconChain;
use block_proposer::PollOutcome as BlockPollOutcome;
use block_proposer::{BlockProducer, Error as BlockPollError};
use db::MemoryDB;
use direct_beacon_node::DirectBeaconNode;
use direct_duties::DirectDuties;
use fork_choice::BitwiseLMDGhost;
use local_signer::LocalSigner;
use slot_clock::TestingSlotClock;
use std::sync::Arc;
use types::{BeaconBlock, ChainSpec, Keypair, Slot};
#[derive(Debug, PartialEq)]
pub enum BlockProduceError {
DidNotProduce(BlockPollOutcome),
PollError(BlockPollError),
}
type TestingBlockProducer = BlockProducer<
TestingSlotClock,
DirectBeaconNode<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>,
DirectDuties<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>,
LocalSigner,
>;
type TestingAttester = Attester<
TestingSlotClock,
DirectBeaconNode<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>,
DirectDuties<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>,
LocalSigner,
>;
/// A `BlockProducer` and `Attester` which sign using a common keypair.
///
/// The test validator connects directly to a borrowed `BeaconChain` struct. It is useful for
/// testing that the core proposer and attester logic is functioning. Also for supporting beacon
/// chain tests.
pub struct ValidatorHarness {
pub block_producer: TestingBlockProducer,
pub attester: TestingAttester,
pub spec: Arc<ChainSpec>,
pub epoch_map: Arc<DirectDuties<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>>,
pub keypair: Keypair,
pub beacon_node: Arc<DirectBeaconNode<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>>,
pub slot_clock: Arc<TestingSlotClock>,
pub signer: Arc<LocalSigner>,
}
impl ValidatorHarness {
/// Create a new ValidatorHarness that signs with the given keypair, operates per the given spec and connects to the
/// supplied beacon node.
///
/// A `BlockProducer` and `Attester` is created..
pub fn new(
keypair: Keypair,
beacon_chain: Arc<BeaconChain<MemoryDB, TestingSlotClock, BitwiseLMDGhost<MemoryDB>>>,
spec: Arc<ChainSpec>,
) -> Self {
let slot_clock = Arc::new(TestingSlotClock::new(spec.genesis_slot.as_u64()));
let signer = Arc::new(LocalSigner::new(keypair.clone()));
let beacon_node = Arc::new(DirectBeaconNode::new(beacon_chain.clone()));
let epoch_map = Arc::new(DirectDuties::new(keypair.pk.clone(), beacon_chain.clone()));
let block_producer = BlockProducer::new(
spec.clone(),
epoch_map.clone(),
slot_clock.clone(),
beacon_node.clone(),
signer.clone(),
);
let attester = Attester::new(
epoch_map.clone(),
slot_clock.clone(),
beacon_node.clone(),
signer.clone(),
);
Self {
block_producer,
attester,
spec,
epoch_map,
keypair,
beacon_node,
slot_clock,
signer,
}
}
/// Run the `poll` function on the `BlockProducer` and produce a block.
///
/// An error is returned if the producer refuses to produce.
pub fn produce_block(&mut self) -> Result<BeaconBlock, BlockProduceError> {
// Using `DirectBeaconNode`, the validator will always return sucessufully if it tries to
// publish a block.
match self.block_producer.poll() {
Ok(BlockPollOutcome::BlockProduced(_)) => {}
Ok(outcome) => return Err(BlockProduceError::DidNotProduce(outcome)),
Err(error) => return Err(BlockProduceError::PollError(error)),
};
Ok(self
.beacon_node
.last_published_block()
.expect("Unable to obtain produced block."))
}
/// Set the validators slot clock to the specified slot.
///
/// The validators slot clock will always read this value until it is set to something else.
pub fn set_slot(&mut self, slot: Slot) {
self.slot_clock.set_slot(slot.as_u64())
}
}

View File

@@ -1,46 +0,0 @@
#![cfg(not(debug_assertions))]
use env_logger::{Builder, Env};
use log::debug;
use test_harness::BeaconChainHarness;
use types::ChainSpec;
#[test]
fn it_can_build_on_genesis_block() {
Builder::from_env(Env::default().default_filter_or("info")).init();
let spec = ChainSpec::few_validators();
let validator_count = 8;
let mut harness = BeaconChainHarness::new(spec, validator_count as usize);
harness.advance_chain_with_block();
}
#[test]
#[ignore]
fn it_can_produce_past_first_epoch_boundary() {
Builder::from_env(Env::default().default_filter_or("info")).init();
let spec = ChainSpec::few_validators();
let validator_count = 8;
debug!("Starting harness build...");
let mut harness = BeaconChainHarness::new(spec, validator_count);
debug!("Harness built, tests starting..");
let blocks = harness.spec.slots_per_epoch * 2 + 1;
for i in 0..blocks {
harness.advance_chain_with_block();
debug!("Produced block {}/{}.", i + 1, blocks);
}
harness.run_fork_choice();
let dump = harness.chain_dump().expect("Chain dump failed.");
assert_eq!(dump.len() as u64, blocks + 1); // + 1 for genesis block.
}