Merge branch 'master' into ci-rustfmt

This commit is contained in:
Luke Anderson 2019-03-21 16:06:09 +11:00
commit 129631886b
No known key found for this signature in database
GPG Key ID: 44408169EC61E228
206 changed files with 8478 additions and 4834 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
indent_style=space
indent_size=4
end_of_line=lf
charset=utf-8
trim_trailing_whitespace=true
max_line_length=100
insert_final_newline=false

View File

@ -11,6 +11,7 @@ members = [
"eth2/utils/honey-badger-split",
"eth2/utils/merkle_proof",
"eth2/utils/int_to_bytes",
"eth2/utils/serde_hex",
"eth2/utils/slot_clock",
"eth2/utils/ssz",
"eth2/utils/ssz_derive",
@ -19,6 +20,11 @@ members = [
"eth2/utils/test_random_derive",
"beacon_node",
"beacon_node/db",
"beacon_node/client",
"beacon_node/network",
"beacon_node/eth2-libp2p",
"beacon_node/rpc",
"beacon_node/version",
"beacon_node/beacon_chain",
"beacon_node/beacon_chain/test_harness",
"protos",

View File

@ -1,24 +1,19 @@
[package]
name = "beacon_node"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com"]
edition = "2018"
[dependencies]
bls = { path = "../eth2/utils/bls" }
beacon_chain = { path = "beacon_chain" }
grpcio = { version = "0.4", default-features = false, features = ["protobuf-codec"] }
protobuf = "2.0.2"
protos = { path = "../protos" }
types = { path = "../eth2/types" }
client = { path = "client" }
version = { path = "version" }
clap = "2.32.0"
db = { path = "db" }
dirs = "1.0.3"
futures = "0.1.23"
fork_choice = { path = "../eth2/fork_choice" }
slog = "^2.2.3"
slot_clock = { path = "../eth2/utils/slot_clock" }
slog-term = "^2.4.0"
slog-async = "^2.3.0"
types = { path = "../eth2/types" }
ssz = { path = "../eth2/utils/ssz" }
tokio = "0.1"
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,7 +1,7 @@
[package]
name = "beacon_chain"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com>"]
edition = "2018"
[dependencies]

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 {
@ -85,20 +82,18 @@ where
let state_root = genesis_state.canonical_root();
state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?;
let block_root = genesis_block.canonical_root();
let block_root = genesis_block.block_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,
));
@ -106,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,
@ -192,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.block_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(())
}
@ -248,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
@ -273,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)
}
@ -287,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,
})
}
@ -564,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.block_header().canonical_root();
let present_slot = self.present_slot();
@ -635,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,
@ -647,23 +585,20 @@ 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;
let previous_block_header = parent_block.block_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),
));
@ -739,22 +674,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,
@ -802,6 +737,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

@ -0,0 +1,94 @@
// Initialisation functions to generate a new BeaconChain.
// Note: A new version of ClientTypes may need to be implemented for the lighthouse
// testnet. These are examples. Also. there is code duplication which can/should be cleaned up.
use crate::BeaconChain;
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::test_utils::TestingBeaconStateBuilder;
use types::{BeaconBlock, ChainSpec, 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>>> {
// set up the db
let db = Arc::new(DiskDB::open(
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 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());
// Genesis chain
//TODO: Handle error correctly
Arc::new(
BeaconChain::from_genesis(
state_store.clone(),
block_store.clone(),
slot_clock,
genesis_state,
genesis_block,
spec.clone(),
fork_choice,
)
.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(
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 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());
// Genesis chain
//TODO: Handle error correctly
Arc::new(
BeaconChain::from_genesis(
state_store.clone(),
block_store.clone(),
slot_clock,
genesis_state,
genesis_block,
spec.clone(),
fork_choice,
)
.expect("Terminate if beacon chain generation fails"),
)
}

View File

@ -2,8 +2,13 @@ mod attestation_aggregator;
mod beacon_chain;
mod checkpoint;
mod errors;
pub mod initialise;
pub use self::beacon_chain::{BeaconChain, BlockProcessingOutcome, InvalidBlock, ValidBlock};
pub use self::checkpoint::CheckPoint;
pub use self::errors::BeaconChainError;
pub use fork_choice::{ForkChoice, ForkChoiceAlgorithm, ForkChoiceError};
pub use db;
pub use fork_choice;
pub use parking_lot;
pub use slot_clock;
pub use types;

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]

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

@ -9,6 +9,7 @@ test_cases:
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

View File

@ -1,7 +1,6 @@
use super::ValidatorHarness;
use beacon_chain::{BeaconChain, BlockProcessingOutcome};
pub use beacon_chain::{BeaconChainError, CheckPoint};
use bls::get_withdrawal_credentials;
use db::{
stores::{BeaconBlockStore, BeaconStateStore},
MemoryDB,
@ -12,15 +11,9 @@ use rayon::prelude::*;
use slot_clock::TestingSlotClock;
use ssz::TreeHash;
use std::collections::HashSet;
use std::fs::File;
use std::iter::FromIterator;
use std::path::Path;
use std::sync::Arc;
use types::{beacon_state::BeaconStateBuilder, test_utils::generate_deterministic_keypairs, *};
mod generate_deposits;
pub use generate_deposits::generate_deposits_from_keypairs;
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
@ -42,97 +35,19 @@ impl BeaconChainHarness {
///
/// - 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,
validators_dir: Option<&Path>,
skip_deposit_verification: bool,
) -> Self {
pub fn new(spec: ChainSpec, validator_count: usize) -> 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 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(),
};
let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec);
let state_builder =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
let (genesis_state, keypairs) = state_builder.build();
// If a `validators_dir` is specified, load the keypairs a YAML file.
//
// Otherwise, generate them deterministically where the first validator has a secret key of
// `1`, etc.
let keypairs = if let Some(path) = validators_dir {
debug!("Loading validator keypairs from file...");
let keypairs_file = File::open(path.join("keypairs.yaml")).unwrap();
let mut keypairs: Vec<Keypair> = serde_yaml::from_reader(&keypairs_file).unwrap();
keypairs.truncate(validator_count);
keypairs
} else {
debug!("Generating validator keypairs...");
generate_deterministic_keypairs(validator_count)
};
// Skipping deposit verification means directly generating `Validator` records, instead
// of generating `Deposit` objects, verifying them and converting them into `Validator`
// records.
//
// It is much faster to skip deposit verification, however it does not test the initial
// validator induction part of beacon chain genesis.
if skip_deposit_verification {
let validators = keypairs
.iter()
.map(|keypair| {
let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(
&keypair.pk,
spec.bls_withdrawal_prefix_byte,
));
Validator {
pubkey: keypair.pk.clone(),
withdrawal_credentials,
activation_epoch: spec.far_future_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
initiated_exit: false,
slashed: false,
}
})
.collect();
let balances = vec![32_000_000_000; validator_count];
state_builder.import_existing_validators(
validators,
balances,
validator_count as u64,
&spec,
);
} else {
debug!("Generating initial validator deposits...");
let deposits = generate_deposits_from_keypairs(
&keypairs,
genesis_time,
spec.get_domain(
spec.genesis_epoch,
Domain::Deposit,
&Fork {
previous_version: spec.genesis_fork_version,
current_version: spec.genesis_fork_version,
epoch: spec.genesis_epoch,
},
),
&spec,
);
state_builder.process_initial_deposits(&deposits, &spec);
};
let genesis_state = state_builder.build(&spec).unwrap();
let state_root = Hash256::from_slice(&genesis_state.hash_tree_root());
let genesis_block = BeaconBlock::genesis(state_root, &spec);
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(
@ -212,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> =
@ -318,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,46 +0,0 @@
use bls::get_withdrawal_credentials;
use log::debug;
use rayon::prelude::*;
use types::*;
/// Generates a `Deposit` for each keypairs
pub fn generate_deposits_from_keypairs(
keypairs: &[Keypair],
genesis_time: u64,
domain: u64,
spec: &ChainSpec,
) -> Vec<Deposit> {
debug!(
"Generating {} validator deposits from random keypairs...",
keypairs.len()
);
let initial_validator_deposits = keypairs
.par_iter()
.map(|keypair| {
let withdrawal_credentials = Hash256::from_slice(
&get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..],
);
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: withdrawal_credentials.clone(),
proof_of_possession: DepositInput::create_proof_of_possession(
&keypair,
&withdrawal_credentials,
domain,
),
},
},
}
})
.collect();
initial_validator_deposits
}

View File

@ -15,7 +15,7 @@
//! let validator_count = 8;
//! let spec = ChainSpec::few_validators();
//!
//! let mut harness = BeaconChainHarness::new(spec, validator_count, None, true);
//! let mut harness = BeaconChainHarness::new(spec, validator_count);
//!
//! harness.advance_chain_with_block();
//!

View File

@ -1,6 +1,5 @@
use crate::test_case::TestCase;
use clap::ArgMatches;
use std::path::Path;
use std::{fs::File, io::prelude::*};
use yaml_rust::YamlLoader;
@ -17,10 +16,6 @@ pub fn run_test(matches: &ArgMatches) {
};
for doc in &docs {
let validators_dir = matches
.value_of("validators_dir")
.and_then(|dir_str| Some(Path::new(dir_str)));
// For each `test_cases` YAML in the document, build a `TestCase`, execute it and
// assert that the execution result matches the test_case description.
//
@ -35,7 +30,7 @@ pub fn run_test(matches: &ArgMatches) {
// 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(validators_dir))
test_case.assert_result_valid(test_case.execute())
}
}
}

View File

@ -3,13 +3,11 @@
use crate::beacon_chain_harness::BeaconChainHarness;
use beacon_chain::CheckPoint;
use bls::get_withdrawal_credentials;
use log::{info, warn};
use ssz::SignedRoot;
use std::path::Path;
use types::*;
use types::test_utils::{TestingAttesterSlashingBuilder, TestingProposerSlashingBuilder};
use types::test_utils::*;
use yaml_rust::Yaml;
mod config;
@ -64,12 +62,16 @@ impl TestCase {
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, validators_dir: Option<&Path>) -> ExecutionResult {
pub fn execute(&self) -> ExecutionResult {
let spec = self.spec();
let validator_count = self.config.deposits_for_chain_start;
let slots = self.config.num_slots;
@ -79,7 +81,7 @@ impl TestCase {
validator_count
);
let mut harness = BeaconChainHarness::new(spec, validator_count, validators_dir, true);
let mut harness = BeaconChainHarness::new(spec, validator_count);
info!("Starting simulation across {} slots...", slots);
@ -223,27 +225,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`.
@ -256,41 +251,12 @@ fn build_deposit(
index_offset: u64,
) -> (Deposit, Keypair) {
let keypair = Keypair::random();
let withdrawal_credentials = Hash256::from_slice(
&get_withdrawal_credentials(&keypair.pk, harness.spec.bls_withdrawal_prefix_byte)[..],
);
let proof_of_possession = DepositInput::create_proof_of_possession(
&keypair,
&withdrawal_credentials,
harness.spec.get_domain(
harness
.beacon_chain
.state
.read()
.current_epoch(&harness.spec),
Domain::Deposit,
&harness.beacon_chain.state.read().fork,
),
);
let index = harness.beacon_chain.state.read().deposit_index + index_offset;
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`.

View File

@ -20,6 +20,8 @@ pub struct Config {
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.
@ -45,6 +47,7 @@ impl Config {
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),

View File

@ -10,7 +10,7 @@ fn it_can_build_on_genesis_block() {
let spec = ChainSpec::few_validators();
let validator_count = 8;
let mut harness = BeaconChainHarness::new(spec, validator_count as usize, None, true);
let mut harness = BeaconChainHarness::new(spec, validator_count as usize);
harness.advance_chain_with_block();
}
@ -25,7 +25,7 @@ fn it_can_produce_past_first_epoch_boundary() {
debug!("Starting harness build...");
let mut harness = BeaconChainHarness::new(spec, validator_count, None, true);
let mut harness = BeaconChainHarness::new(spec, validator_count);
debug!("Harness built, tests starting..");

View File

@ -0,0 +1,21 @@
[package]
name = "client"
version = "0.1.0"
authors = ["Age Manning <Age@AgeManning.com>"]
edition = "2018"
[dependencies]
beacon_chain = { path = "../beacon_chain" }
network = { path = "../network" }
db = { path = "../db" }
rpc = { path = "../rpc" }
fork_choice = { path = "../../eth2/fork_choice" }
types = { path = "../../eth2/types" }
slot_clock = { path = "../../eth2/utils/slot_clock" }
error-chain = "0.12.0"
slog = "^2.2.3"
tokio = "0.1.15"
clap = "2.32.0"
dirs = "1.0.3"
exit-future = "0.1.3"
futures = "0.1.25"

View File

@ -0,0 +1,124 @@
use clap::ArgMatches;
use db::DBType;
use fork_choice::ForkChoiceAlgorithm;
use network::NetworkConfig;
use slog::error;
use std::fs;
use std::net::SocketAddr;
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
use types::multiaddr::Protocol;
use types::multiaddr::ToMultiaddr;
use types::ChainSpec;
/// Stores the client configuration for this Lighthouse instance.
#[derive(Debug, Clone)]
pub struct ClientConfig {
pub data_dir: PathBuf,
pub spec: ChainSpec,
pub net_conf: network::NetworkConfig,
pub fork_choice: ForkChoiceAlgorithm,
pub db_type: DBType,
pub db_name: PathBuf,
pub rpc_conf: rpc::RPCConfig,
//pub ipc_conf:
}
impl Default for ClientConfig {
/// Build a new lighthouse configuration from defaults.
fn default() -> Self {
let data_dir = {
let home = dirs::home_dir().expect("Unable to determine home dir.");
home.join(".lighthouse/")
};
fs::create_dir_all(&data_dir)
.unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir));
let default_spec = ChainSpec::lighthouse_testnet();
let default_net_conf = NetworkConfig::new(default_spec.boot_nodes.clone());
Self {
data_dir: data_dir.clone(),
// default to foundation for chain specs
spec: default_spec,
net_conf: default_net_conf,
// default to bitwise LMD Ghost
fork_choice: ForkChoiceAlgorithm::BitwiseLMDGhost,
// default to memory db for now
db_type: DBType::Memory,
// default db name for disk-based dbs
db_name: data_dir.join("chain.db"),
rpc_conf: rpc::RPCConfig::default(),
}
}
}
impl ClientConfig {
/// Parses the CLI arguments into a `Config` struct.
pub fn parse_args(args: ArgMatches, log: &slog::Logger) -> Result<Self, &'static str> {
let mut config = ClientConfig::default();
/* Network related arguments */
// Custom p2p listen port
if let Some(port_str) = args.value_of("port") {
if let Ok(port) = port_str.parse::<u16>() {
config.net_conf.listen_port = port;
// update the listening multiaddrs
for address in &mut config.net_conf.listen_addresses {
address.pop();
address.append(Protocol::Tcp(port));
}
} else {
error!(log, "Invalid port"; "port" => port_str);
return Err("Invalid port");
}
}
// Custom listening address ipv4/ipv6
// TODO: Handle list of addresses
if let Some(listen_address_str) = args.value_of("listen_address") {
if let Ok(listen_address) = listen_address_str.parse::<IpAddr>() {
let multiaddr = SocketAddr::new(listen_address, config.net_conf.listen_port)
.to_multiaddr()
.expect("Invalid listen address format");
config.net_conf.listen_addresses = vec![multiaddr];
} else {
error!(log, "Invalid IP Address"; "Address" => listen_address_str);
return Err("Invalid IP Address");
}
}
/* Filesystem related arguments */
// Custom datadir
if let Some(dir) = args.value_of("datadir") {
config.data_dir = PathBuf::from(dir.to_string());
};
/* RPC related arguments */
if args.is_present("rpc") {
config.rpc_conf.enabled = true;
}
if let Some(rpc_address) = args.value_of("rpc-address") {
if let Ok(listen_address) = rpc_address.parse::<Ipv4Addr>() {
config.rpc_conf.listen_address = listen_address;
} else {
error!(log, "Invalid RPC listen address"; "Address" => rpc_address);
return Err("Invalid RPC listen address");
}
}
if let Some(rpc_port) = args.value_of("rpc-port") {
if let Ok(port) = rpc_port.parse::<u16>() {
config.rpc_conf.port = port;
} else {
error!(log, "Invalid RPC port"; "port" => rpc_port);
return Err("Invalid RPC port");
}
}
Ok(config)
}
}

View File

@ -0,0 +1,49 @@
use crate::ClientConfig;
use beacon_chain::{
db::{ClientDB, DiskDB, MemoryDB},
fork_choice::BitwiseLMDGhost,
initialise,
slot_clock::{SlotClock, SystemTimeSlotClock},
BeaconChain,
};
use fork_choice::ForkChoice;
use std::sync::Arc;
pub trait ClientTypes {
type DB: ClientDB + 'static;
type SlotClock: SlotClock + 'static;
type ForkChoice: ForkChoice + 'static;
fn initialise_beacon_chain(
config: &ClientConfig,
) -> Arc<BeaconChain<Self::DB, Self::SlotClock, Self::ForkChoice>>;
}
pub struct StandardClientType;
impl ClientTypes for StandardClientType {
type DB = DiskDB;
type SlotClock = SystemTimeSlotClock;
type ForkChoice = BitwiseLMDGhost<DiskDB>;
fn initialise_beacon_chain(
config: &ClientConfig,
) -> Arc<BeaconChain<Self::DB, Self::SlotClock, Self::ForkChoice>> {
initialise::initialise_beacon_chain(&config.spec, Some(&config.db_name))
}
}
pub struct TestingClientType;
impl ClientTypes for TestingClientType {
type DB = MemoryDB;
type SlotClock = SystemTimeSlotClock;
type ForkChoice = BitwiseLMDGhost<MemoryDB>;
fn initialise_beacon_chain(
config: &ClientConfig,
) -> Arc<BeaconChain<Self::DB, Self::SlotClock, Self::ForkChoice>> {
initialise::initialise_test_beacon_chain(&config.spec, None)
}
}

View File

@ -0,0 +1,14 @@
// generates error types
use network;
use error_chain::{
error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed,
impl_extract_backtrace,
};
error_chain! {
links {
Network(network::error::Error, network::error::ErrorKind);
}
}

View File

@ -0,0 +1,77 @@
extern crate slog;
mod client_config;
pub mod client_types;
pub mod error;
pub mod notifier;
use beacon_chain::BeaconChain;
pub use client_config::ClientConfig;
pub use client_types::ClientTypes;
use exit_future::Signal;
use network::Service as NetworkService;
use slog::o;
use std::marker::PhantomData;
use std::sync::Arc;
use tokio::runtime::TaskExecutor;
/// Main beacon node client service. This provides the connection and initialisation of the clients
/// sub-services in multiple threads.
pub struct Client<T: ClientTypes> {
/// Configuration for the lighthouse client.
config: ClientConfig,
/// The beacon chain for the running client.
beacon_chain: Arc<BeaconChain<T::DB, T::SlotClock, T::ForkChoice>>,
/// Reference to the network service.
pub network: Arc<NetworkService>,
/// Future to stop and begin shutdown of the Client.
//TODO: Decide best way to handle shutdown
pub exit: exit_future::Exit,
/// The sending future to call to terminate the Client.
//TODO: Decide best way to handle shutdown
pub exit_signal: Signal,
/// The clients logger.
log: slog::Logger,
/// Marker to pin the beacon chain generics.
phantom: PhantomData<T>,
}
impl<TClientType: ClientTypes> Client<TClientType> {
/// Generate an instance of the client. Spawn and link all internal sub-processes.
pub fn new(
config: ClientConfig,
log: slog::Logger,
executor: &TaskExecutor,
) -> error::Result<Self> {
let (exit_signal, exit) = exit_future::signal();
// generate a beacon chain
let beacon_chain = TClientType::initialise_beacon_chain(&config);
// Start the network service, libp2p and syncing threads
// TODO: Add beacon_chain reference to network parameters
let network_config = &config.net_conf;
let network_logger = log.new(o!("Service" => "Network"));
let (network, _network_send) = NetworkService::new(
beacon_chain.clone(),
network_config,
executor,
network_logger,
)?;
// spawn the RPC server
if config.rpc_conf.enabled {
rpc::start_server(&config.rpc_conf, &log);
}
Ok(Client {
config,
beacon_chain,
exit,
exit_signal,
log,
network,
phantom: PhantomData,
})
}
}

View File

@ -0,0 +1,45 @@
use crate::Client;
use crate::ClientTypes;
use exit_future::Exit;
use futures::{Future, Stream};
use slog::{debug, info, o};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tokio::runtime::TaskExecutor;
use tokio::timer::Interval;
/// Thread that monitors the client and reports useful statistics to the user.
pub fn run<T: ClientTypes>(client: &Client<T>, executor: TaskExecutor, exit: Exit) {
// notification heartbeat
let interval = Interval::new(Instant::now(), Duration::from_secs(5));
let log = client.log.new(o!("Service" => "Notifier"));
// TODO: Debugging only
let counter = Arc::new(Mutex::new(0));
let network = client.network.clone();
// build heartbeat logic here
let heartbeat = move |_| {
info!(log, "Temp heartbeat output");
//TODO: Remove this logic. Testing only
let mut count = counter.lock().unwrap();
*count += 1;
if *count % 5 == 0 {
debug!(log, "Sending Message");
network.send_message();
}
Ok(())
};
// map error and spawn
let log = client.log.clone();
let heartbeat_interval = interval
.map_err(move |e| debug!(log, "Timer error {}", e))
.for_each(heartbeat);
executor.spawn(exit.until(heartbeat_interval).map(|_| ()));
}

View File

@ -12,3 +12,10 @@ use self::stores::COLUMNS;
pub use self::disk_db::DiskDB;
pub use self::memory_db::MemoryDB;
pub use self::traits::{ClientDB, DBError, DBValue};
/// Currently available database options
#[derive(Debug, Clone)]
pub enum DBType {
Memory,
RocksDB,
}

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> {
}
}
/// Retuns an object implementing `BeaconBlockReader`, or `None` (if hash not known).
///
/// Note: Presently, this function fully deserializes a `BeaconBlock` and returns that. In the
/// future, it would be ideal to return an object capable of reading directly from serialized
/// SSZ bytes.
pub fn get_reader(&self, hash: &Hash256) -> Result<Option<impl BeaconBlockReader>, DBError> {
match self.get(&hash)? {
None => Ok(None),
Some(ssz) => {
let (block, _) = BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| DBError {
message: "Bad BeaconBlock SSZ.".to_string(),
})?;
Ok(Some(block))
}
}
}
/// Retrieve the block at a slot given a "head_hash" and a slot.
///
/// 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);
}

View File

@ -0,0 +1,17 @@
[package]
name = "eth2-libp2p"
version = "0.1.0"
authors = ["Age Manning <Age@AgeManning.com>"]
edition = "2018"
[dependencies]
# SigP repository until PR is merged
libp2p = { git = "https://github.com/SigP/rust-libp2p", branch = "gossipsub" }
types = { path = "../../eth2/types" }
ssz = { path = "../../eth2/utils/ssz" }
ssz_derive = { path = "../../eth2/utils/ssz_derive" }
slog = "2.4.1"
version = { path = "../version" }
tokio = "0.1.16"
futures = "0.1.25"
error-chain = "0.12.0"

View File

@ -0,0 +1,96 @@
use crate::rpc::{RPCEvent, RPCMessage, Rpc};
use futures::prelude::*;
use libp2p::{
core::swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess},
gossipsub::{Gossipsub, GossipsubConfig, GossipsubEvent},
tokio_io::{AsyncRead, AsyncWrite},
NetworkBehaviour, PeerId,
};
use types::Topic;
/// Builds the network behaviour for the libp2p Swarm.
/// Implements gossipsub message routing.
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "BehaviourEvent", poll_method = "poll")]
pub struct Behaviour<TSubstream: AsyncRead + AsyncWrite> {
gossipsub: Gossipsub<TSubstream>,
// TODO: Add Kademlia for peer discovery
/// The events generated by this behaviour to be consumed in the swarm poll.
serenity_rpc: Rpc<TSubstream>,
#[behaviour(ignore)]
events: Vec<BehaviourEvent>,
}
// Implement the NetworkBehaviourEventProcess trait so that we can derive NetworkBehaviour for Behaviour
impl<TSubstream: AsyncRead + AsyncWrite> NetworkBehaviourEventProcess<GossipsubEvent>
for Behaviour<TSubstream>
{
fn inject_event(&mut self, event: GossipsubEvent) {
match event {
GossipsubEvent::Message(message) => {
let gs_message = String::from_utf8_lossy(&message.data);
// TODO: Remove this type - debug only
self.events
.push(BehaviourEvent::Message(gs_message.to_string()))
}
_ => {}
}
}
}
impl<TSubstream: AsyncRead + AsyncWrite> NetworkBehaviourEventProcess<RPCMessage>
for Behaviour<TSubstream>
{
fn inject_event(&mut self, event: RPCMessage) {
match event {
RPCMessage::PeerDialed(peer_id) => {
self.events.push(BehaviourEvent::PeerDialed(peer_id))
}
RPCMessage::RPC(peer_id, rpc_event) => {
self.events.push(BehaviourEvent::RPC(peer_id, rpc_event))
}
}
}
}
impl<TSubstream: AsyncRead + AsyncWrite> Behaviour<TSubstream> {
pub fn new(local_peer_id: PeerId, gs_config: GossipsubConfig, log: &slog::Logger) -> Self {
Behaviour {
gossipsub: Gossipsub::new(local_peer_id, gs_config),
serenity_rpc: Rpc::new(log),
events: Vec::new(),
}
}
/// Consumes the events list when polled.
fn poll<TBehaviourIn>(
&mut self,
) -> Async<NetworkBehaviourAction<TBehaviourIn, BehaviourEvent>> {
if !self.events.is_empty() {
return Async::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0)));
}
Async::NotReady
}
}
/// Implements the combined behaviour for the libp2p service.
impl<TSubstream: AsyncRead + AsyncWrite> Behaviour<TSubstream> {
/// Subscribes to a gossipsub topic.
pub fn subscribe(&mut self, topic: Topic) -> bool {
self.gossipsub.subscribe(topic)
}
/// Sends an RPC Request/Response via the RPC protocol.
pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) {
self.serenity_rpc.send_rpc(peer_id, rpc_event);
}
}
/// The types of events than can be obtained from polling the behaviour.
pub enum BehaviourEvent {
RPC(PeerId, RPCEvent),
PeerDialed(PeerId),
// TODO: This is a stub at the moment
Message(String),
}

View File

@ -0,0 +1,8 @@
// generates error types
use error_chain::{
error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed,
impl_extract_backtrace,
};
error_chain! {}

View File

@ -0,0 +1,20 @@
/// This crate contains the main link for lighthouse to rust-libp2p. It therefore re-exports
/// all required libp2p functionality.
///
/// This crate builds and manages the libp2p services required by the beacon node.
pub mod behaviour;
pub mod error;
mod network_config;
pub mod rpc;
mod service;
pub use libp2p::{
gossipsub::{GossipsubConfig, GossipsubConfigBuilder},
PeerId,
};
pub use network_config::NetworkConfig;
pub use rpc::{HelloMessage, RPCEvent};
pub use service::Libp2pEvent;
pub use service::Service;
pub use types::multiaddr;
pub use types::Multiaddr;

View File

@ -0,0 +1,59 @@
use crate::Multiaddr;
use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder};
use libp2p::secio;
use std::fmt;
#[derive(Clone)]
/// Network configuration for lighthouse.
pub struct NetworkConfig {
//TODO: stubbing networking initial params, change in the future
/// IP address to listen on.
pub listen_addresses: Vec<Multiaddr>,
/// Listen port UDP/TCP.
pub listen_port: u16,
/// Gossipsub configuration parameters.
pub gs_config: GossipsubConfig,
/// List of nodes to initially connect to.
pub boot_nodes: Vec<Multiaddr>,
/// Peer key related to this nodes PeerId.
pub local_private_key: secio::SecioKeyPair,
/// Client version
pub client_version: String,
/// List of topics to subscribe to as strings
pub topics: Vec<String>,
}
impl Default for NetworkConfig {
/// Generate a default network configuration.
fn default() -> Self {
// TODO: Currently using secp256k1 key pairs. Wire protocol specifies RSA. Waiting for this
// PR to be merged to generate RSA keys: https://github.com/briansmith/ring/pull/733
NetworkConfig {
listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000"
.parse()
.expect("is a correct multi-address")],
listen_port: 9000,
gs_config: GossipsubConfigBuilder::new().build(),
boot_nodes: Vec::new(),
local_private_key: secio::SecioKeyPair::secp256k1_generated().unwrap(),
client_version: version::version(),
topics: vec![String::from("beacon_chain")],
}
}
}
impl NetworkConfig {
pub fn new(boot_nodes: Vec<Multiaddr>) -> Self {
let mut conf = NetworkConfig::default();
conf.boot_nodes = boot_nodes;
conf
}
}
impl fmt::Debug for NetworkConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "NetworkConfig: listen_addresses: {:?}, listen_port: {:?}, gs_config: {:?}, boot_nodes: {:?}, local_private_key: <Secio-PubKey {:?}>, client_version: {:?}", self.listen_addresses, self.listen_port, self.gs_config, self.boot_nodes, self.local_private_key.to_public_key(), self.client_version)
}
}

View File

@ -0,0 +1,161 @@
/// Available RPC methods types and ids.
use ssz_derive::{Decode, Encode};
use types::{BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot};
#[derive(Debug)]
/// Available Serenity Libp2p RPC methods
pub enum RPCMethod {
/// Initialise handshake between connecting peers.
Hello,
/// Terminate a connection providing a reason.
Goodbye,
/// Requests a number of beacon block roots.
BeaconBlockRoots,
/// Requests a number of beacon block headers.
BeaconBlockHeaders,
/// Requests a number of beacon block bodies.
BeaconBlockBodies,
/// Requests values for a merkle proof for the current blocks state root.
BeaconChainState, // Note: experimental, not complete.
/// Unknown method received.
Unknown,
}
impl From<u16> for RPCMethod {
fn from(method_id: u16) -> Self {
match method_id {
0 => RPCMethod::Hello,
1 => RPCMethod::Goodbye,
10 => RPCMethod::BeaconBlockRoots,
11 => RPCMethod::BeaconBlockHeaders,
12 => RPCMethod::BeaconBlockBodies,
13 => RPCMethod::BeaconChainState,
_ => RPCMethod::Unknown,
}
}
}
impl Into<u16> for RPCMethod {
fn into(self) -> u16 {
match self {
RPCMethod::Hello => 0,
RPCMethod::Goodbye => 1,
RPCMethod::BeaconBlockRoots => 10,
RPCMethod::BeaconBlockHeaders => 11,
RPCMethod::BeaconBlockBodies => 12,
RPCMethod::BeaconChainState => 13,
_ => 0,
}
}
}
#[derive(Debug, Clone)]
pub enum RPCRequest {
Hello(HelloMessage),
Goodbye(u64),
BeaconBlockRoots(BeaconBlockRootsRequest),
BeaconBlockHeaders(BeaconBlockHeadersRequest),
BeaconBlockBodies(BeaconBlockBodiesRequest),
BeaconChainState(BeaconChainStateRequest),
}
#[derive(Debug, Clone)]
pub enum RPCResponse {
Hello(HelloMessage),
BeaconBlockRoots(BeaconBlockRootsResponse),
BeaconBlockHeaders(BeaconBlockHeadersResponse),
BeaconBlockBodies(BeaconBlockBodiesResponse),
BeaconChainState(BeaconChainStateResponse),
}
/* Request/Response data structures for RPC methods */
/// The HELLO request/response handshake message.
#[derive(Encode, Decode, Clone, Debug)]
pub struct HelloMessage {
/// The network ID of the peer.
pub network_id: u8,
/// The peers last finalized root.
pub latest_finalized_root: Hash256,
/// The peers last finalized epoch.
pub latest_finalized_epoch: Epoch,
/// The peers last block root.
pub best_root: Hash256,
/// The peers last slot.
pub best_slot: Slot,
}
/// Request a number of beacon block roots from a peer.
#[derive(Encode, Decode, Clone, Debug)]
pub struct BeaconBlockRootsRequest {
/// The starting slot of the requested blocks.
start_slot: Slot,
/// The number of blocks from the start slot.
count: u64, // this must be less than 32768. //TODO: Enforce this in the lower layers
}
/// Response containing a number of beacon block roots from a peer.
#[derive(Encode, Decode, Clone, Debug)]
pub struct BeaconBlockRootsResponse {
/// List of requested blocks and associated slots.
roots: Vec<BlockRootSlot>,
}
/// Contains a block root and associated slot.
#[derive(Encode, Decode, Clone, Debug)]
pub struct BlockRootSlot {
/// The block root.
block_root: Hash256,
/// The block slot.
slot: Slot,
}
/// Request a number of beacon block headers from a peer.
#[derive(Encode, Decode, Clone, Debug)]
pub struct BeaconBlockHeadersRequest {
/// The starting header hash of the requested headers.
start_root: Hash256,
/// The starting slot of the requested headers.
start_slot: Slot,
/// The maximum number of headers than can be returned.
max_headers: u64,
/// The maximum number of slots to skip between blocks.
skip_slots: u64,
}
/// Response containing requested block headers.
#[derive(Encode, Decode, Clone, Debug)]
pub struct BeaconBlockHeadersResponse {
/// The list of requested beacon block headers.
headers: Vec<BeaconBlockHeader>,
}
/// Request a number of beacon block bodies from a peer.
#[derive(Encode, Decode, Clone, Debug)]
pub struct BeaconBlockBodiesRequest {
/// The list of beacon block bodies being requested.
block_roots: Hash256,
}
/// Response containing the list of requested beacon block bodies.
#[derive(Encode, Decode, Clone, Debug)]
pub struct BeaconBlockBodiesResponse {
/// The list of beacon block bodies being requested.
block_bodies: Vec<BeaconBlockBody>,
}
/// Request values for tree hashes which yield a blocks `state_root`.
#[derive(Encode, Decode, Clone, Debug)]
pub struct BeaconChainStateRequest {
/// The tree hashes that a value is requested for.
hashes: Vec<Hash256>,
}
/// Request values for tree hashes which yield a blocks `state_root`.
// Note: TBD
#[derive(Encode, Decode, Clone, Debug)]
pub struct BeaconChainStateResponse {
/// The values corresponding the to the requested tree hashes.
values: bool, //TBD - stubbed with encodeable bool
}

View File

@ -0,0 +1,138 @@
/// RPC Protocol over libp2p.
///
/// This is purpose built for Ethereum 2.0 serenity and the protocol listens on
/// `/eth/serenity/rpc/1.0.0`
mod methods;
mod protocol;
use futures::prelude::*;
use libp2p::core::protocols_handler::{OneShotHandler, ProtocolsHandler};
use libp2p::core::swarm::{
ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters,
};
use libp2p::{Multiaddr, PeerId};
pub use methods::{HelloMessage, RPCMethod, RPCRequest, RPCResponse};
pub use protocol::{RPCEvent, RPCProtocol};
use slog::o;
use std::marker::PhantomData;
use tokio::io::{AsyncRead, AsyncWrite};
/// The network behaviour handles RPC requests/responses as specified in the Eth 2.0 phase 0
/// specification.
pub struct Rpc<TSubstream> {
/// Queue of events to processed.
events: Vec<NetworkBehaviourAction<RPCEvent, RPCMessage>>,
/// Pins the generic substream.
marker: PhantomData<TSubstream>,
/// Slog logger for RPC behaviour.
log: slog::Logger,
}
impl<TSubstream> Rpc<TSubstream> {
pub fn new(log: &slog::Logger) -> Self {
let log = log.new(o!("Service" => "Libp2p-RPC"));
Rpc {
events: Vec::new(),
marker: PhantomData,
log,
}
}
/// Submits and RPC request.
pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) {
self.events.push(NetworkBehaviourAction::SendEvent {
peer_id,
event: rpc_event,
});
}
}
impl<TSubstream> NetworkBehaviour for Rpc<TSubstream>
where
TSubstream: AsyncRead + AsyncWrite,
{
type ProtocolsHandler = OneShotHandler<TSubstream, RPCProtocol, RPCEvent, OneShotEvent>;
type OutEvent = RPCMessage;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
Default::default()
}
fn addresses_of_peer(&mut self, _peer_id: &PeerId) -> Vec<Multiaddr> {
Vec::new()
}
fn inject_connected(&mut self, peer_id: PeerId, connected_point: ConnectedPoint) {
// if initialised the connection, report this upwards to send the HELLO request
if let ConnectedPoint::Dialer { address: _ } = connected_point {
self.events.push(NetworkBehaviourAction::GenerateEvent(
RPCMessage::PeerDialed(peer_id),
));
}
}
fn inject_disconnected(&mut self, _: &PeerId, _: ConnectedPoint) {}
fn inject_node_event(
&mut self,
source: PeerId,
event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
) {
// ignore successful send events
let event = match event {
OneShotEvent::Rx(event) => event,
OneShotEvent::Sent => return,
};
// send the event to the user
self.events
.push(NetworkBehaviourAction::GenerateEvent(RPCMessage::RPC(
source, event,
)));
}
fn poll(
&mut self,
_: &mut PollParameters<'_>,
) -> Async<
NetworkBehaviourAction<
<Self::ProtocolsHandler as ProtocolsHandler>::InEvent,
Self::OutEvent,
>,
> {
if !self.events.is_empty() {
return Async::Ready(self.events.remove(0));
}
Async::NotReady
}
}
/// Messages sent to the user from the RPC protocol.
pub enum RPCMessage {
RPC(PeerId, RPCEvent),
PeerDialed(PeerId),
}
/// Transmission between the `OneShotHandler` and the `RPCEvent`.
#[derive(Debug)]
pub enum OneShotEvent {
/// We received an RPC from a remote.
Rx(RPCEvent),
/// We successfully sent an RPC request.
Sent,
}
impl From<RPCEvent> for OneShotEvent {
#[inline]
fn from(rpc: RPCEvent) -> OneShotEvent {
OneShotEvent::Rx(rpc)
}
}
impl From<()> for OneShotEvent {
#[inline]
fn from(_: ()) -> OneShotEvent {
OneShotEvent::Sent
}
}

View File

@ -0,0 +1,181 @@
use super::methods::{HelloMessage, RPCMethod, RPCRequest, RPCResponse};
use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo};
use ssz::{ssz_encode, Decodable, Encodable, SszStream};
use std::io;
use std::iter;
use tokio::io::{AsyncRead, AsyncWrite};
/// The maximum bytes that can be sent across the RPC.
const MAX_READ_SIZE: usize = 2048;
/// Implementation of the `ConnectionUpgrade` for the rpc protocol.
#[derive(Debug, Clone)]
pub struct RPCProtocol;
impl UpgradeInfo for RPCProtocol {
type Info = &'static [u8];
type InfoIter = iter::Once<Self::Info>;
#[inline]
fn protocol_info(&self) -> Self::InfoIter {
iter::once(b"/eth/serenity/rpc/1.0.0")
}
}
impl Default for RPCProtocol {
fn default() -> Self {
RPCProtocol
}
}
/// The RPC types which are sent/received in this protocol.
#[derive(Debug, Clone)]
pub enum RPCEvent {
Request {
id: u64,
method_id: u16,
body: RPCRequest,
},
Response {
id: u64,
method_id: u16, //TODO: Remove and process decoding upstream
result: RPCResponse,
},
}
impl UpgradeInfo for RPCEvent {
type Info = &'static [u8];
type InfoIter = iter::Once<Self::Info>;
#[inline]
fn protocol_info(&self) -> Self::InfoIter {
iter::once(b"/eth/serenity/rpc/1.0.0")
}
}
impl<TSocket> InboundUpgrade<TSocket> for RPCProtocol
where
TSocket: AsyncRead + AsyncWrite,
{
type Output = RPCEvent;
type Error = DecodeError;
type Future =
upgrade::ReadOneThen<TSocket, (), fn(Vec<u8>, ()) -> Result<RPCEvent, DecodeError>>;
fn upgrade_inbound(self, socket: TSocket, _: Self::Info) -> Self::Future {
upgrade::read_one_then(socket, MAX_READ_SIZE, (), |packet, ()| Ok(decode(packet)?))
}
}
fn decode(packet: Vec<u8>) -> Result<RPCEvent, DecodeError> {
// decode the header of the rpc
// request/response
let (request, index) = bool::ssz_decode(&packet, 0)?;
let (id, index) = u64::ssz_decode(&packet, index)?;
let (method_id, index) = u16::ssz_decode(&packet, index)?;
if request {
let body = match RPCMethod::from(method_id) {
RPCMethod::Hello => {
let (hello_body, _index) = HelloMessage::ssz_decode(&packet, index)?;
RPCRequest::Hello(hello_body)
}
RPCMethod::Unknown | _ => return Err(DecodeError::UnknownRPCMethod),
};
Ok(RPCEvent::Request {
id,
method_id,
body,
})
}
// we have received a response
else {
let result = match RPCMethod::from(method_id) {
RPCMethod::Hello => {
let (body, _index) = HelloMessage::ssz_decode(&packet, index)?;
RPCResponse::Hello(body)
}
RPCMethod::Unknown | _ => return Err(DecodeError::UnknownRPCMethod),
};
Ok(RPCEvent::Response {
id,
method_id,
result,
})
}
}
impl<TSocket> OutboundUpgrade<TSocket> for RPCEvent
where
TSocket: AsyncWrite,
{
type Output = ();
type Error = io::Error;
type Future = upgrade::WriteOne<TSocket>;
#[inline]
fn upgrade_outbound(self, socket: TSocket, _: Self::Info) -> Self::Future {
let bytes = ssz_encode(&self);
upgrade::write_one(socket, bytes)
}
}
impl Encodable for RPCEvent {
fn ssz_append(&self, s: &mut SszStream) {
match self {
RPCEvent::Request {
id,
method_id,
body,
} => {
s.append(&true);
s.append(id);
s.append(method_id);
match body {
RPCRequest::Hello(body) => {
s.append(body);
}
_ => {}
}
}
RPCEvent::Response {
id,
method_id,
result,
} => {
s.append(&false);
s.append(id);
s.append(method_id);
match result {
RPCResponse::Hello(response) => {
s.append(response);
}
_ => {}
}
}
}
}
}
#[derive(Debug)]
pub enum DecodeError {
ReadError(upgrade::ReadOneError),
SSZDecodeError(ssz::DecodeError),
UnknownRPCMethod,
}
impl From<upgrade::ReadOneError> for DecodeError {
#[inline]
fn from(err: upgrade::ReadOneError) -> Self {
DecodeError::ReadError(err)
}
}
impl From<ssz::DecodeError> for DecodeError {
#[inline]
fn from(err: ssz::DecodeError) -> Self {
DecodeError::SSZDecodeError(err)
}
}

View File

@ -0,0 +1,163 @@
use crate::behaviour::{Behaviour, BehaviourEvent};
use crate::error;
use crate::multiaddr::Protocol;
use crate::rpc::RPCEvent;
use crate::NetworkConfig;
use futures::prelude::*;
use futures::Stream;
use libp2p::core::{
muxing::StreamMuxerBox,
nodes::Substream,
transport::boxed::Boxed,
upgrade::{InboundUpgradeExt, OutboundUpgradeExt},
};
use libp2p::{core, secio, Transport};
use libp2p::{PeerId, Swarm};
use slog::{debug, info, trace, warn};
use std::io::{Error, ErrorKind};
use std::time::Duration;
use types::TopicBuilder;
/// The configuration and state of the libp2p components for the beacon node.
pub struct Service {
/// The libp2p Swarm handler.
//TODO: Make this private
pub swarm: Swarm<Boxed<(PeerId, StreamMuxerBox), Error>, Behaviour<Substream<StreamMuxerBox>>>,
/// This node's PeerId.
local_peer_id: PeerId,
/// The libp2p logger handle.
pub log: slog::Logger,
}
impl Service {
pub fn new(config: NetworkConfig, log: slog::Logger) -> error::Result<Self> {
debug!(log, "Libp2p Service starting");
let local_private_key = config.local_private_key;
let local_peer_id = local_private_key.to_peer_id();
info!(log, "Local peer id: {:?}", local_peer_id);
let mut swarm = {
// Set up the transport
let transport = build_transport(local_private_key);
// Set up gossipsub routing
let behaviour = Behaviour::new(local_peer_id.clone(), config.gs_config, &log);
// Set up Topology
let topology = local_peer_id.clone();
Swarm::new(transport, behaviour, topology)
};
// listen on all addresses
for address in &config.listen_addresses {
match Swarm::listen_on(&mut swarm, address.clone()) {
Ok(mut listen_addr) => {
listen_addr.append(Protocol::P2p(local_peer_id.clone().into()));
info!(log, "Listening on: {}", listen_addr);
}
Err(err) => warn!(log, "Cannot listen on: {} : {:?}", address, err),
};
}
// connect to boot nodes - these are currently stored as multiaddrs
// Once we have discovery, can set to peerId
for bootnode in config.boot_nodes {
match Swarm::dial_addr(&mut swarm, bootnode.clone()) {
Ok(()) => debug!(log, "Dialing bootnode: {}", bootnode),
Err(err) => debug!(
log,
"Could not connect to bootnode: {} error: {:?}", bootnode, err
),
};
}
// subscribe to default gossipsub topics
let mut subscribed_topics = vec![];
for topic in config.topics {
let t = TopicBuilder::new(topic.to_string()).build();
if swarm.subscribe(t) {
trace!(log, "Subscribed to topic: {:?}", topic);
subscribed_topics.push(topic);
} else {
warn!(log, "Could not subscribe to topic: {:?}", topic)
}
}
info!(log, "Subscribed to topics: {:?}", subscribed_topics);
Ok(Service {
local_peer_id,
swarm,
log,
})
}
}
impl Stream for Service {
type Item = Libp2pEvent;
type Error = crate::error::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
loop {
// TODO: Currently only gossipsub events passed here.
// Build a type for more generic events
match self.swarm.poll() {
Ok(Async::Ready(Some(BehaviourEvent::Message(m)))) => {
// TODO: Stub here for debugging
debug!(self.log, "Message received: {}", m);
return Ok(Async::Ready(Some(Libp2pEvent::Message(m))));
}
Ok(Async::Ready(Some(BehaviourEvent::RPC(peer_id, event)))) => {
return Ok(Async::Ready(Some(Libp2pEvent::RPC(peer_id, event))));
}
Ok(Async::Ready(Some(BehaviourEvent::PeerDialed(peer_id)))) => {
return Ok(Async::Ready(Some(Libp2pEvent::PeerDialed(peer_id))));
}
Ok(Async::Ready(None)) => unreachable!("Swarm stream shouldn't end"),
Ok(Async::NotReady) => break,
_ => break,
}
}
Ok(Async::NotReady)
}
}
/// The implementation supports TCP/IP, WebSockets over TCP/IP, secio as the encryption layer, and
/// mplex or yamux as the multiplexing layer.
fn build_transport(
local_private_key: secio::SecioKeyPair,
) -> Boxed<(PeerId, StreamMuxerBox), Error> {
// TODO: The Wire protocol currently doesn't specify encryption and this will need to be customised
// in the future.
let transport = libp2p::tcp::TcpConfig::new();
let transport = libp2p::dns::DnsConfig::new(transport);
#[cfg(feature = "libp2p-websocket")]
let transport = {
let trans_clone = transport.clone();
transport.or_transport(websocket::WsConfig::new(trans_clone))
};
transport
.with_upgrade(secio::SecioConfig::new(local_private_key))
.and_then(move |out, endpoint| {
let peer_id = out.remote_key.into_peer_id();
let peer_id2 = peer_id.clone();
let upgrade = core::upgrade::SelectUpgrade::new(
libp2p::yamux::Config::default(),
libp2p::mplex::MplexConfig::new(),
)
// TODO: use a single `.map` instead of two maps
.map_inbound(move |muxer| (peer_id, muxer))
.map_outbound(move |muxer| (peer_id2, muxer));
core::upgrade::apply(out.stream, upgrade, endpoint)
.map(|(id, muxer)| (id, core::muxing::StreamMuxerBox::new(muxer)))
})
.with_timeout(Duration::from_secs(20))
.map_err(|err| Error::new(ErrorKind::Other, err))
.boxed()
}
/// Events that can be obtained from polling the Libp2p Service.
pub enum Libp2pEvent {
// We have received an RPC event on the swarm
RPC(PeerId, RPCEvent),
PeerDialed(PeerId),
Message(String),
}

View File

@ -0,0 +1,16 @@
[package]
name = "network"
version = "0.1.0"
authors = ["Age Manning <Age@AgeManning.com>"]
edition = "2018"
[dependencies]
beacon_chain = { path = "../beacon_chain" }
eth2-libp2p = { path = "../eth2-libp2p" }
version = { path = "../version" }
types = { path = "../../eth2/types" }
slog = "2.4.1"
futures = "0.1.25"
error-chain = "0.12.0"
crossbeam-channel = "0.3.8"
tokio = "0.1.16"

View File

@ -0,0 +1,43 @@
use beacon_chain::BeaconChain as RawBeaconChain;
use beacon_chain::{
db::ClientDB,
fork_choice::ForkChoice,
parking_lot::RwLockReadGuard,
slot_clock::SlotClock,
types::{BeaconState, ChainSpec},
CheckPoint,
};
/// The network's API to the beacon chain.
pub trait BeaconChain: Send + Sync {
fn get_spec(&self) -> &ChainSpec;
fn get_state(&self) -> RwLockReadGuard<BeaconState>;
fn head(&self) -> RwLockReadGuard<CheckPoint>;
fn finalized_head(&self) -> RwLockReadGuard<CheckPoint>;
}
impl<T, U, F> BeaconChain for RawBeaconChain<T, U, F>
where
T: ClientDB + Sized,
U: SlotClock,
F: ForkChoice,
{
fn get_spec(&self) -> &ChainSpec {
&self.spec
}
fn get_state(&self) -> RwLockReadGuard<BeaconState> {
self.state.read()
}
fn head(&self) -> RwLockReadGuard<CheckPoint> {
self.head()
}
fn finalized_head(&self) -> RwLockReadGuard<CheckPoint> {
self.finalized_head()
}
}

View File

@ -0,0 +1,13 @@
// generates error types
use eth2_libp2p;
use error_chain::{
error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed,
impl_extract_backtrace,
};
error_chain! {
links {
Libp2p(eth2_libp2p::error::Error, eth2_libp2p::error::ErrorKind);
}
}

View File

@ -0,0 +1,9 @@
/// This crate provides the network server for Lighthouse.
pub mod beacon_chain;
pub mod error;
mod message_handler;
mod service;
pub mod sync;
pub use eth2_libp2p::NetworkConfig;
pub use service::Service;

View File

@ -0,0 +1,225 @@
use crate::beacon_chain::BeaconChain;
use crate::error;
use crate::service::{NetworkMessage, OutgoingMessage};
use crate::sync::SimpleSync;
use crossbeam_channel::{unbounded as channel, Sender};
use eth2_libp2p::{
rpc::{RPCMethod, RPCRequest, RPCResponse},
HelloMessage, PeerId, RPCEvent,
};
use futures::future;
use slog::warn;
use slog::{debug, trace};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
/// Timeout for RPC requests.
const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
/// Timeout before banning a peer for non-identification.
const HELLO_TIMEOUT: Duration = Duration::from_secs(30);
/// Handles messages received from the network and client and organises syncing.
pub struct MessageHandler {
/// Currently loaded and initialised beacon chain.
chain: Arc<BeaconChain>,
/// The syncing framework.
sync: SimpleSync,
/// The network channel to relay messages to the Network service.
network_send: crossbeam_channel::Sender<NetworkMessage>,
/// A mapping of peers and the RPC id we have sent an RPC request to.
requests: HashMap<(PeerId, u64), Instant>,
/// A counter of request id for each peer.
request_ids: HashMap<PeerId, u64>,
/// The `MessageHandler` logger.
log: slog::Logger,
}
/// Types of messages the handler can receive.
#[derive(Debug, Clone)]
pub enum HandlerMessage {
/// We have initiated a connection to a new peer.
PeerDialed(PeerId),
/// Peer has disconnected,
PeerDisconnected(PeerId),
/// An RPC response/request has been received.
RPC(PeerId, RPCEvent),
/// A block has been imported.
BlockImported(), //TODO: This comes from pub-sub - decide its contents
}
impl MessageHandler {
/// Initializes and runs the MessageHandler.
pub fn spawn(
beacon_chain: Arc<BeaconChain>,
network_send: crossbeam_channel::Sender<NetworkMessage>,
executor: &tokio::runtime::TaskExecutor,
log: slog::Logger,
) -> error::Result<Sender<HandlerMessage>> {
debug!(log, "Service starting");
let (handler_send, handler_recv) = channel();
// Initialise sync and begin processing in thread
// generate the Message handler
let sync = SimpleSync::new(beacon_chain.clone(), &log);
let mut handler = MessageHandler {
// TODO: The handler may not need a chain, perhaps only sync?
chain: beacon_chain.clone(),
sync,
network_send,
requests: HashMap::new(),
request_ids: HashMap::new(),
log: log.clone(),
};
// spawn handler task
// TODO: Handle manual termination of thread
executor.spawn(future::poll_fn(move || -> Result<_, _> {
loop {
handler.handle_message(handler_recv.recv().map_err(|_| {
debug!(log, "Network message handler terminated.");
})?);
}
}));
Ok(handler_send)
}
/// Handle all messages incoming from the network service.
fn handle_message(&mut self, message: HandlerMessage) {
match message {
// we have initiated a connection to a peer
HandlerMessage::PeerDialed(peer_id) => {
let id = self.generate_request_id(&peer_id);
self.send_hello(peer_id, id, true);
}
// we have received an RPC message request/response
HandlerMessage::RPC(peer_id, rpc_event) => {
self.handle_rpc_message(peer_id, rpc_event);
}
//TODO: Handle all messages
_ => {}
}
}
/* RPC - Related functionality */
/// Handle RPC messages
fn handle_rpc_message(&mut self, peer_id: PeerId, rpc_message: RPCEvent) {
match rpc_message {
RPCEvent::Request { id, body, .. // TODO: Clean up RPC Message types, have a cleaner type by this point.
} => self.handle_rpc_request(peer_id, id, body),
RPCEvent::Response { id, result, .. } => self.handle_rpc_response(peer_id, id, result),
}
}
/// A new RPC request has been received from the network.
fn handle_rpc_request(&mut self, peer_id: PeerId, id: u64, request: RPCRequest) {
match request {
RPCRequest::Hello(hello_message) => {
self.handle_hello_request(peer_id, id, hello_message)
}
// TODO: Handle all requests
_ => {}
}
}
/// An RPC response has been received from the network.
// we match on id and ignore responses past the timeout.
fn handle_rpc_response(&mut self, peer_id: PeerId, id: u64, response: RPCResponse) {
// if response id is related to a request, ignore (likely RPC timeout)
if self.requests.remove(&(peer_id.clone(), id)).is_none() {
debug!(self.log, "Unrecognized response from peer: {:?}", peer_id);
return;
}
match response {
RPCResponse::Hello(hello_message) => {
debug!(self.log, "Hello response received from peer: {:?}", peer_id);
self.validate_hello(peer_id, hello_message);
}
// TODO: Handle all responses
_ => {}
}
}
/// Handle a HELLO RPC request message.
fn handle_hello_request(&mut self, peer_id: PeerId, id: u64, hello_message: HelloMessage) {
// send back a HELLO message
self.send_hello(peer_id.clone(), id, false);
// validate the peer
self.validate_hello(peer_id, hello_message);
}
/// Validate a HELLO RPC message.
fn validate_hello(&mut self, peer_id: PeerId, message: HelloMessage) {
// validate the peer
if !self.sync.validate_peer(peer_id.clone(), message) {
debug!(
self.log,
"Peer dropped due to mismatching HELLO messages: {:?}", peer_id
);
//TODO: block/ban the peer
}
}
/* General RPC helper functions */
/// Generates a new request id for a peer.
fn generate_request_id(&mut self, peer_id: &PeerId) -> u64 {
// generate a unique id for the peer
let id = {
let borrowed_id = self.request_ids.entry(peer_id.clone()).or_insert_with(|| 0);
let id = borrowed_id.clone();
//increment the counter
*borrowed_id += 1;
id
};
// register RPC request
self.requests.insert((peer_id.clone(), id), Instant::now());
debug!(
self.log,
"Hello request registered with peer: {:?}", peer_id
);
id
}
/// Sends a HELLO RPC request or response to a newly connected peer.
//TODO: The boolean determines if sending request/respond, will be cleaner in the RPC re-write
fn send_hello(&mut self, peer_id: PeerId, id: u64, is_request: bool) {
let rpc_event = if is_request {
RPCEvent::Request {
id,
method_id: RPCMethod::Hello.into(),
body: RPCRequest::Hello(self.sync.generate_hello()),
}
} else {
RPCEvent::Response {
id,
method_id: RPCMethod::Hello.into(),
result: RPCResponse::Hello(self.sync.generate_hello()),
}
};
// send the hello request to the network
trace!(self.log, "Sending HELLO message to peer {:?}", peer_id);
self.send_rpc(peer_id, rpc_event);
}
/// Sends an RPC request/response to the network server.
fn send_rpc(&self, peer_id: PeerId, rpc_event: RPCEvent) {
self.network_send
.send(NetworkMessage::Send(
peer_id,
OutgoingMessage::RPC(rpc_event),
))
.unwrap_or_else(|_| {
warn!(
self.log,
"Could not send RPC message to the network service"
)
});
}
}

View File

@ -0,0 +1,180 @@
use crate::beacon_chain::BeaconChain;
use crate::error;
use crate::message_handler::{HandlerMessage, MessageHandler};
use crate::NetworkConfig;
use crossbeam_channel::{unbounded as channel, Sender, TryRecvError};
use eth2_libp2p::RPCEvent;
use eth2_libp2p::Service as LibP2PService;
use eth2_libp2p::{Libp2pEvent, PeerId};
use futures::prelude::*;
use futures::sync::oneshot;
use futures::Stream;
use slog::{debug, info, o, trace};
use std::sync::Arc;
use tokio::runtime::TaskExecutor;
/// Service that handles communication between internal services and the eth2_libp2p network service.
pub struct Service {
//eth2_libp2p_service: Arc<Mutex<LibP2PService>>,
eth2_libp2p_exit: oneshot::Sender<()>,
network_send: crossbeam_channel::Sender<NetworkMessage>,
//message_handler: MessageHandler,
//message_handler_send: Sender<HandlerMessage>,
}
impl Service {
pub fn new(
beacon_chain: Arc<BeaconChain>,
config: &NetworkConfig,
executor: &TaskExecutor,
log: slog::Logger,
) -> error::Result<(Arc<Self>, Sender<NetworkMessage>)> {
// build the network channel
let (network_send, network_recv) = channel::<NetworkMessage>();
// launch message handler thread
let message_handler_log = log.new(o!("Service" => "MessageHandler"));
let message_handler_send = MessageHandler::spawn(
beacon_chain,
network_send.clone(),
executor,
message_handler_log,
)?;
// launch eth2_libp2p service
let eth2_libp2p_log = log.new(o!("Service" => "Libp2p"));
let eth2_libp2p_service = LibP2PService::new(config.clone(), eth2_libp2p_log)?;
// TODO: Spawn thread to handle eth2_libp2p messages and pass to message handler thread.
let eth2_libp2p_exit = spawn_service(
eth2_libp2p_service,
network_recv,
message_handler_send,
executor,
log,
)?;
let network_service = Service {
eth2_libp2p_exit,
network_send: network_send.clone(),
};
Ok((Arc::new(network_service), network_send))
}
// TODO: Testing only
pub fn send_message(&self) {
self.network_send
.send(NetworkMessage::Send(
PeerId::random(),
OutgoingMessage::NotifierTest,
))
.unwrap();
}
}
fn spawn_service(
eth2_libp2p_service: LibP2PService,
network_recv: crossbeam_channel::Receiver<NetworkMessage>,
message_handler_send: crossbeam_channel::Sender<HandlerMessage>,
executor: &TaskExecutor,
log: slog::Logger,
) -> error::Result<oneshot::Sender<()>> {
let (network_exit, exit_rx) = oneshot::channel();
// spawn on the current executor
executor.spawn(
network_service(
eth2_libp2p_service,
network_recv,
message_handler_send,
log.clone(),
)
// allow for manual termination
.select(exit_rx.then(|_| Ok(())))
.then(move |_| {
info!(log.clone(), "Network service shutdown");
Ok(())
}),
);
Ok(network_exit)
}
fn network_service(
mut eth2_libp2p_service: LibP2PService,
network_recv: crossbeam_channel::Receiver<NetworkMessage>,
message_handler_send: crossbeam_channel::Sender<HandlerMessage>,
log: slog::Logger,
) -> impl futures::Future<Item = (), Error = eth2_libp2p::error::Error> {
futures::future::poll_fn(move || -> Result<_, eth2_libp2p::error::Error> {
// poll the swarm
loop {
match eth2_libp2p_service.poll() {
Ok(Async::Ready(Some(Libp2pEvent::RPC(peer_id, rpc_event)))) => {
trace!(
eth2_libp2p_service.log,
"RPC Event: RPC message received: {:?}",
rpc_event
);
message_handler_send
.send(HandlerMessage::RPC(peer_id, rpc_event))
.map_err(|_| "failed to send rpc to handler")?;
}
Ok(Async::Ready(Some(Libp2pEvent::PeerDialed(peer_id)))) => {
debug!(eth2_libp2p_service.log, "Peer Dialed: {:?}", peer_id);
message_handler_send
.send(HandlerMessage::PeerDialed(peer_id))
.map_err(|_| "failed to send rpc to handler")?;
}
Ok(Async::Ready(Some(Libp2pEvent::Message(m)))) => debug!(
eth2_libp2p_service.log,
"Network Service: Message received: {}", m
),
_ => break,
}
}
// poll the network channel
// TODO: refactor - combine poll_fn's?
loop {
match network_recv.try_recv() {
// TODO: Testing message - remove
Ok(NetworkMessage::Send(peer_id, outgoing_message)) => {
match outgoing_message {
OutgoingMessage::RPC(rpc_event) => {
trace!(log, "Sending RPC Event: {:?}", rpc_event);
//TODO: Make swarm private
//TODO: Implement correct peer id topic message handling
eth2_libp2p_service.swarm.send_rpc(peer_id, rpc_event);
}
OutgoingMessage::NotifierTest => {
debug!(log, "Received message from notifier");
}
};
}
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => {
return Err(eth2_libp2p::error::Error::from(
"Network channel disconnected",
));
}
}
}
Ok(Async::NotReady)
})
}
/// Types of messages that the network service can receive.
#[derive(Debug, Clone)]
pub enum NetworkMessage {
/// Send a message to eth2_libp2p service.
//TODO: Define typing for messages across the wire
Send(PeerId, OutgoingMessage),
}
/// Type of outgoing messages that can be sent through the network service.
#[derive(Debug, Clone)]
pub enum OutgoingMessage {
/// Send an RPC request/response.
RPC(RPCEvent),
//TODO: Remove
NotifierTest,
}

View File

@ -0,0 +1,11 @@
/// Syncing for lighthouse.
///
/// Stores the various syncing methods for the beacon chain.
mod simple_sync;
pub use simple_sync::SimpleSync;
/// Currently implemented sync methods.
pub enum SyncMethod {
SimpleSync,
}

View File

@ -0,0 +1,112 @@
use crate::beacon_chain::BeaconChain;
use eth2_libp2p::rpc::HelloMessage;
use eth2_libp2p::PeerId;
use slog::{debug, o};
use std::collections::HashMap;
use std::sync::Arc;
use types::{Epoch, Hash256, Slot};
/// The number of slots that we can import blocks ahead of us, before going into full Sync mode.
const SLOT_IMPORT_TOLERANCE: u64 = 100;
/// Keeps track of syncing information for known connected peers.
pub struct PeerSyncInfo {
latest_finalized_root: Hash256,
latest_finalized_epoch: Epoch,
best_root: Hash256,
best_slot: Slot,
}
/// The current syncing state.
#[derive(PartialEq)]
pub enum SyncState {
Idle,
Downloading,
Stopped,
}
/// Simple Syncing protocol.
//TODO: Decide for HELLO messages whether its better to keep current in RAM or build on the fly
//when asked.
pub struct SimpleSync {
/// A reference to the underlying beacon chain.
chain: Arc<BeaconChain>,
/// A mapping of Peers to their respective PeerSyncInfo.
known_peers: HashMap<PeerId, PeerSyncInfo>,
/// The current state of the syncing protocol.
state: SyncState,
/// The network id, for quick HELLO RPC message lookup.
network_id: u8,
/// The latest epoch of the syncing chain.
latest_finalized_epoch: Epoch,
/// The latest block of the syncing chain.
latest_slot: Slot,
/// Sync logger.
log: slog::Logger,
}
impl SimpleSync {
pub fn new(beacon_chain: Arc<BeaconChain>, log: &slog::Logger) -> Self {
let state = beacon_chain.get_state();
let sync_logger = log.new(o!("Service"=> "Sync"));
SimpleSync {
chain: beacon_chain.clone(),
known_peers: HashMap::new(),
state: SyncState::Idle,
network_id: beacon_chain.get_spec().network_id,
latest_finalized_epoch: state.finalized_epoch,
latest_slot: state.slot - 1, //TODO: Build latest block function into Beacon chain and correct this
log: sync_logger,
}
}
/// Generates our current state in the form of a HELLO RPC message.
pub fn generate_hello(&self) -> HelloMessage {
let state = &self.chain.get_state();
//TODO: Paul to verify the logic of these fields.
HelloMessage {
network_id: self.network_id,
latest_finalized_root: state.finalized_root,
latest_finalized_epoch: state.finalized_epoch,
best_root: Hash256::zero(), //TODO: build correct value as a beacon chain function
best_slot: state.slot - 1,
}
}
pub fn validate_peer(&mut self, peer_id: PeerId, hello_message: HelloMessage) -> bool {
// network id must match
if hello_message.network_id != self.network_id {
return false;
}
// compare latest epoch and finalized root to see if they exist in our chain
if hello_message.latest_finalized_epoch <= self.latest_finalized_epoch {
// ensure their finalized root is in our chain
// TODO: Get the finalized root at hello_message.latest_epoch and ensure they match
//if (hello_message.latest_finalized_root == self.chain.get_state() {
// return false;
// }
}
// the client is valid, add it to our list of known_peers and request sync if required
// update peer list if peer already exists
let peer_info = PeerSyncInfo {
latest_finalized_root: hello_message.latest_finalized_root,
latest_finalized_epoch: hello_message.latest_finalized_epoch,
best_root: hello_message.best_root,
best_slot: hello_message.best_slot,
};
debug!(self.log, "Handshake successful. Peer: {:?}", peer_id);
self.known_peers.insert(peer_id, peer_info);
// set state to sync
if self.state == SyncState::Idle
&& hello_message.best_slot > self.latest_slot + SLOT_IMPORT_TOLERANCE
{
self.state = SyncState::Downloading;
//TODO: Start requesting blocks from known peers. Ideally in batches
}
true
}
}

View File

@ -0,0 +1,23 @@
[package]
name = "rpc"
version = "0.1.0"
authors = ["Age Manning <Age@AgeManning.com>"]
edition = "2018"
[dependencies]
bls = { path = "../../eth2/utils/bls" }
beacon_chain = { path = "../beacon_chain" }
protos = { path = "../../protos" }
grpcio = { version = "0.4", default-features = false, features = ["protobuf-codec"] }
protobuf = "2.0.2"
clap = "2.32.0"
db = { path = "../db" }
dirs = "1.0.3"
futures = "0.1.23"
slog = "^2.2.3"
slot_clock = { path = "../../eth2/utils/slot_clock" }
slog-term = "^2.4.0"
slog-async = "^2.3.0"
types = { path = "../../eth2/types" }
ssz = { path = "../../eth2/utils/ssz" }

View File

@ -0,0 +1,22 @@
use std::net::Ipv4Addr;
/// RPC Configuration
#[derive(Debug, Clone)]
pub struct Config {
/// Enable the RPC server.
pub enabled: bool,
/// The IPv4 address the RPC will listen on.
pub listen_address: Ipv4Addr,
/// The port the RPC will listen on.
pub port: u16,
}
impl Default for Config {
fn default() -> Self {
Config {
enabled: false, // rpc disabled by default
listen_address: Ipv4Addr::new(127, 0, 0, 1),
port: 5051,
}
}
}

View File

@ -1,16 +1,18 @@
mod beacon_block;
pub mod config;
mod validator;
use self::beacon_block::BeaconBlockServiceInstance;
use self::validator::ValidatorServiceInstance;
pub use config::Config as RPCConfig;
use grpcio::{Environment, Server, ServerBuilder};
use protos::services_grpc::{create_beacon_block_service, create_validator_service};
use std::sync::Arc;
use slog::{info, Logger};
use slog::{info, o};
pub fn start_server(log: Logger) -> Server {
let log_clone = log.clone();
pub fn start_server(config: &RPCConfig, log: &slog::Logger) -> Server {
let log = log.new(o!("Service"=>"RPC"));
let env = Arc::new(Environment::new(1));
let beacon_block_service = {
@ -25,12 +27,12 @@ pub fn start_server(log: Logger) -> Server {
let mut server = ServerBuilder::new(env)
.register_service(beacon_block_service)
.register_service(validator_service)
.bind("127.0.0.1", 50_051)
.bind(config.listen_address.to_string(), config.port)
.build()
.unwrap();
server.start();
for &(ref host, port) in server.bind_addrs() {
info!(log_clone, "gRPC listening on {}:{}", host, port);
info!(log, "gRPC listening on {}:{}", host, port);
}
server
}

View File

@ -1,30 +0,0 @@
use std::fs;
use std::path::PathBuf;
/// Stores the core configuration for this Lighthouse instance.
/// This struct is general, other components may implement more
/// specialized config structs.
#[derive(Clone)]
pub struct LighthouseConfig {
pub data_dir: PathBuf,
pub p2p_listen_port: u16,
}
const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse";
impl LighthouseConfig {
/// Build a new lighthouse configuration from defaults.
pub fn default() -> Self {
let data_dir = {
let home = dirs::home_dir().expect("Unable to determine home dir.");
home.join(DEFAULT_LIGHTHOUSE_DIR)
};
fs::create_dir_all(&data_dir)
.unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir));
let p2p_listen_port = 0;
Self {
data_dir,
p2p_listen_port,
}
}
}

View File

@ -1,37 +1,20 @@
extern crate slog;
mod config;
mod rpc;
mod run;
use std::path::PathBuf;
use crate::config::LighthouseConfig;
use crate::rpc::start_server;
use beacon_chain::BeaconChain;
use clap::{App, Arg};
use db::{
stores::{BeaconBlockStore, BeaconStateStore},
MemoryDB,
};
use fork_choice::BitwiseLMDGhost;
use slog::{error, info, o, Drain};
use slot_clock::SystemTimeSlotClock;
use ssz::TreeHash;
use std::sync::Arc;
use types::{
beacon_state::BeaconStateBuilder, BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput,
Domain, Eth1Data, Fork, Hash256, Keypair,
};
use client::ClientConfig;
use slog::{error, o, Drain};
fn main() {
let decorator = slog_term::TermDecorator::new().build();
let drain = slog_term::CompactFormat::new(decorator).build().fuse();
let drain = slog_async::Async::new(drain).build().fuse();
let log = slog::Logger::root(drain, o!());
let logger = slog::Logger::root(drain, o!());
let matches = App::new("Lighthouse")
.version("0.0.1")
.author("Sigma Prime <paul@sigmaprime.io>")
.version(version::version().as_str())
.author("Sigma Prime <contact@sigmaprime.io>")
.about("Eth 2.0 Client")
.arg(
Arg::with_name("datadir")
@ -40,6 +23,13 @@ fn main() {
.help("Data directory for keys and databases.")
.takes_value(true),
)
.arg(
Arg::with_name("listen_address")
.long("listen-address")
.value_name("Listen Address")
.help("The Network address to listen for p2p connections.")
.takes_value(true),
)
.arg(
Arg::with_name("port")
.long("port")
@ -47,110 +37,34 @@ fn main() {
.help("Network listen port for p2p connections.")
.takes_value(true),
)
.arg(
Arg::with_name("rpc")
.long("rpc")
.value_name("RPC")
.help("Enable the RPC server.")
.takes_value(false),
)
.arg(
Arg::with_name("rpc-address")
.long("rpc-address")
.value_name("RPCADDRESS")
.help("Listen address for RPC endpoint.")
.takes_value(true),
)
.arg(
Arg::with_name("rpc-port")
.long("rpc-port")
.value_name("RPCPORT")
.help("Listen port for RPC endpoint.")
.takes_value(true),
)
.get_matches();
let mut config = LighthouseConfig::default();
// invalid arguments, panic
let config = ClientConfig::parse_args(matches, &logger).unwrap();
// Custom datadir
if let Some(dir) = matches.value_of("datadir") {
config.data_dir = PathBuf::from(dir.to_string());
}
// Custom p2p listen port
if let Some(port_str) = matches.value_of("port") {
if let Ok(port) = port_str.parse::<u16>() {
config.p2p_listen_port = port;
} else {
error!(log, "Invalid port"; "port" => port_str);
return;
}
}
// Log configuration
info!(log, "";
"data_dir" => &config.data_dir.to_str(),
"port" => &config.p2p_listen_port);
// Specification (presently fixed to foundation).
let spec = ChainSpec::foundation();
// Database (presently in-memory)
let db = Arc::new(MemoryDB::open());
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
let state_store = Arc::new(BeaconStateStore::new(db.clone()));
// Slot clock
let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past).
let slot_clock = SystemTimeSlotClock::new(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: Vec<Deposit> = keypairs
.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(),
withdrawal_credentials: Hash256::zero(), // Withdrawal not possible.
proof_of_possession: DepositInput::create_proof_of_possession(
&keypair,
&Hash256::zero(),
spec.get_domain(
// Get domain from genesis fork_version
spec.genesis_epoch,
Domain::Deposit,
&Fork {
previous_version: spec.genesis_fork_version,
current_version: spec.genesis_fork_version,
epoch: spec.genesis_epoch,
},
),
),
},
},
})
.collect();
let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec);
state_builder.process_initial_deposits(&initial_validator_deposits, &spec);
let genesis_state = state_builder.build(&spec).unwrap();
let state_root = Hash256::from_slice(&genesis_state.hash_tree_root());
let genesis_block = BeaconBlock::genesis(state_root, &spec);
// Genesis chain
let _chain_result = BeaconChain::from_genesis(
state_store.clone(),
block_store.clone(),
slot_clock,
genesis_state,
genesis_block,
spec,
fork_choice,
);
let _server = start_server(log.clone());
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
match run::run_beacon_node(config, &logger) {
Ok(_) => {}
Err(e) => error!(logger, "Beacon node failed because {:?}", e),
}
}

51
beacon_node/src/run.rs Normal file
View File

@ -0,0 +1,51 @@
use client::client_types::TestingClientType;
use client::error;
use client::{notifier, Client, ClientConfig};
use futures::sync::oneshot;
use futures::Future;
use slog::info;
use std::cell::RefCell;
use tokio::runtime::Builder;
pub fn run_beacon_node(config: ClientConfig, log: &slog::Logger) -> error::Result<()> {
let mut runtime = Builder::new()
.name_prefix("main-")
.build()
.map_err(|e| format!("{:?}", e))?;
// Log configuration
info!(log, "Listening on {:?}", &config.net_conf.listen_addresses;
"data_dir" => &config.data_dir.to_str(),
"port" => &config.net_conf.listen_port);
// run service until ctrl-c
let (ctrlc_send, ctrlc) = oneshot::channel();
let ctrlc_send_c = RefCell::new(Some(ctrlc_send));
ctrlc::set_handler(move || {
if let Some(ctrlc_send) = ctrlc_send_c.try_borrow_mut().unwrap().take() {
ctrlc_send.send(()).expect("Error sending ctrl-c message");
}
})
.map_err(|e| format!("Could not set ctrlc hander: {:?}", e))?;
let (exit_signal, exit) = exit_future::signal();
let executor = runtime.executor();
// currently testing - using TestingClientType
let client: Client<TestingClientType> = Client::new(config, log.clone(), &executor)?;
notifier::run(&client, executor, exit);
runtime
.block_on(ctrlc)
.map_err(|e| format!("Ctrlc oneshot failed: {:?}", e))?;
// perform global shutdown operations.
info!(log, "Shutting down..");
exit_signal.fire();
// shutdown the client
// client.exit_signal.fire();
drop(client);
runtime.shutdown_on_idle().wait().unwrap();
Ok(())
}

View File

@ -0,0 +1,8 @@
[package]
name = "version"
version = "0.1.0"
authors = ["Age Manning <Age@AgeManning.com>"]
edition = "2018"
[dependencies]
target_info = "0.1.0"

View File

@ -0,0 +1,25 @@
//TODO: Build the version and hash of the built lighthouse binary
/// Version information for the Lighthouse beacon node.
// currently only supports unstable release
extern crate target_info;
use target_info::Target;
const TRACK: &str = "unstable";
/// Provides the current platform
pub fn platform() -> String {
format!("{}-{}", Target::arch(), Target::os())
}
/// Version of the beacon node.
// TODO: Find the sha3 hash, date and rust version used to build the beacon_node binary
pub fn version() -> String {
format!(
"Lighthouse/v{}-{}/{}",
env!("CARGO_PKG_VERSION"),
TRACK,
platform()
)
}

View File

@ -4,7 +4,7 @@ mod traits;
use slot_clock::SlotClock;
use ssz::{SignedRoot, TreeHash};
use std::sync::Arc;
use types::{BeaconBlock, ChainSpec, Domain, Hash256, Proposal, Slot};
use types::{BeaconBlock, ChainSpec, Domain, Slot};
pub use self::traits::{
BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer,
@ -158,7 +158,7 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
if self.safe_to_produce(&block) {
let domain = self.spec.get_domain(
slot.epoch(self.spec.slots_per_epoch),
Domain::Proposal,
Domain::BeaconBlock,
&fork,
);
if let Some(block) = self.sign_block(block, domain) {
@ -182,16 +182,9 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
fn sign_block(&mut self, mut block: BeaconBlock, domain: u64) -> Option<BeaconBlock> {
self.store_produce(&block);
let proposal = Proposal {
slot: block.slot,
shard: self.spec.beacon_chain_shard_number,
block_root: Hash256::from_slice(&block.signed_root()[..]),
signature: block.signature.clone(),
};
match self
.signer
.sign_block_proposal(&proposal.signed_root()[..], domain)
.sign_block_proposal(&block.signed_root()[..], domain)
{
None => None,
Some(signature) => {

View File

@ -28,8 +28,8 @@ impl DutiesReader for EpochMap {
fn fork(&self) -> Result<Fork, DutiesReaderError> {
Ok(Fork {
previous_version: 0,
current_version: 0,
previous_version: [0; 4],
current_version: [0; 4],
epoch: Epoch::new(0),
})
}

View File

@ -10,10 +10,7 @@ use db::{
use log::{debug, trace};
use std::collections::HashMap;
use std::sync::Arc;
use types::{
readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock,
ChainSpec, Hash256, Slot, SlotHeight,
};
use types::{BeaconBlock, ChainSpec, Hash256, Slot, SlotHeight};
//TODO: Pruning - Children
//TODO: Handle Syncing
@ -93,10 +90,8 @@ where
.get_deserialized(&state_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
let active_validator_indices = get_active_validator_indices(
&current_state.validator_registry[..],
block_slot.epoch(spec.slots_per_epoch),
);
let active_validator_indices =
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
for index in active_validator_indices {
let balance = std::cmp::min(
@ -255,17 +250,17 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
// get the height of the parent
let parent_height = self
.block_store
.get_deserialized(&block.parent_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))?
.slot()
.get_deserialized(&block.previous_block_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.previous_block_root))?
.slot
.height(spec.genesis_slot);
let parent_hash = &block.parent_root;
let parent_hash = &block.previous_block_root;
// add the new block to the children of parent
(*self
.children
.entry(block.parent_root)
.entry(block.previous_block_root)
.or_insert_with(|| vec![]))
.push(block_hash.clone());
@ -309,7 +304,7 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
.block_store
.get_deserialized(&target_block_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))?
.slot()
.slot
.height(spec.genesis_slot);
// get the height of the past target block
@ -317,7 +312,7 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
.block_store
.get_deserialized(&attestation_target)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))?
.slot()
.slot
.height(spec.genesis_slot);
// update the attestation only if the new target is higher
if past_block_height < block_height {
@ -343,8 +338,8 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
.get_deserialized(&justified_block_start)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?;
let block_slot = block.slot();
let state_root = block.state_root();
let block_slot = block.slot;
let state_root = block.state_root;
let mut block_height = block_slot.height(spec.genesis_slot);
let mut current_head = *justified_block_start;
@ -409,11 +404,23 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
*child_votes.entry(child).or_insert_with(|| 0) += vote;
}
}
// given the votes on the children, find the best child
current_head = self
.choose_best_child(&child_votes)
.ok_or(ForkChoiceError::CannotFindBestChild)?;
trace!("Best child found: {}", current_head);
// check if we have votes of children, if not select the smallest hash child
if child_votes.is_empty() {
current_head = *children
.iter()
.min_by(|child1, child2| child1.cmp(child2))
.expect("Must be children here");
trace!(
"Children have no votes - smallest hash chosen: {}",
current_head
);
} else {
// given the votes on the children, find the best child
current_head = self
.choose_best_child(&child_votes)
.ok_or(ForkChoiceError::CannotFindBestChild)?;
trace!("Best child found: {}", current_head);
}
}
// didn't find head yet, proceed to next iteration
@ -422,7 +429,7 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
.block_store
.get_deserialized(&current_head)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))?
.slot()
.slot
.height(spec.genesis_slot);
// prune the latest votes for votes that are not part of current chosen chain
// more specifically, only keep votes that have head as an ancestor

View File

@ -22,6 +22,7 @@ extern crate types;
pub mod bitwise_lmd_ghost;
pub mod longest_chain;
pub mod optimized_lmd_ghost;
pub mod slow_lmd_ghost;
use db::stores::BeaconBlockAtSlotError;
@ -30,6 +31,7 @@ use types::{BeaconBlock, ChainSpec, Hash256};
pub use bitwise_lmd_ghost::BitwiseLMDGhost;
pub use longest_chain::LongestChain;
pub use optimized_lmd_ghost::OptimizedLMDGhost;
pub use slow_lmd_ghost::SlowLMDGhost;
/// Defines the interface for Fork Choices. Each Fork choice will define their own data structures
@ -94,6 +96,7 @@ impl From<BeaconBlockAtSlotError> for ForkChoiceError {
}
/// Fork choice options that are currently implemented.
#[derive(Debug, Clone)]
pub enum ForkChoiceAlgorithm {
/// Chooses the longest chain becomes the head. Not for production.
LongestChain,
@ -101,4 +104,6 @@ pub enum ForkChoiceAlgorithm {
SlowLMDGhost,
/// An optimised version of bitwise LMD-GHOST by Vitalik.
BitwiseLMDGhost,
/// An optimised implementation of LMD ghost.
OptimizedLMDGhost,
}

View File

@ -34,7 +34,7 @@ impl<T: ClientDB + Sized> ForkChoice for LongestChain<T> {
) -> Result<(), ForkChoiceError> {
// add the block hash to head_block_hashes removing the parent if it exists
self.head_block_hashes
.retain(|hash| *hash != block.parent_root);
.retain(|hash| *hash != block.previous_block_root);
self.head_block_hashes.push(*block_hash);
Ok(())
}

View File

@ -0,0 +1,460 @@
//! The optimised bitwise LMD-GHOST fork choice rule.
extern crate bit_vec;
use crate::{ForkChoice, ForkChoiceError};
use db::{
stores::{BeaconBlockStore, BeaconStateStore},
ClientDB,
};
use log::{debug, trace};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::sync::Arc;
use types::{BeaconBlock, ChainSpec, Hash256, Slot, SlotHeight};
//TODO: Pruning - Children
//TODO: Handle Syncing
// NOTE: This uses u32 to represent difference between block heights. Thus this is only
// applicable for block height differences in the range of a u32.
// This can potentially be parallelized in some parts.
/// Compute the base-2 logarithm of an integer, floored (rounded down)
#[inline]
fn log2_int(x: u64) -> u32 {
if x == 0 {
return 0;
}
63 - x.leading_zeros()
}
fn power_of_2_below(x: u64) -> u64 {
2u64.pow(log2_int(x))
}
/// Stores the necessary data structures to run the optimised lmd ghost algorithm.
pub struct OptimizedLMDGhost<T: ClientDB + Sized> {
/// A cache of known ancestors at given heights for a specific block.
//TODO: Consider FnvHashMap
cache: HashMap<CacheKey<u64>, Hash256>,
/// Log lookup table for blocks to their ancestors.
//TODO: Verify we only want/need a size 16 log lookup
ancestors: Vec<HashMap<Hash256, Hash256>>,
/// Stores the children for any given parent.
children: HashMap<Hash256, Vec<Hash256>>,
/// The latest attestation targets as a map of validator index to block hash.
//TODO: Could this be a fixed size vec
latest_attestation_targets: HashMap<u64, Hash256>,
/// Block storage access.
block_store: Arc<BeaconBlockStore<T>>,
/// State storage access.
state_store: Arc<BeaconStateStore<T>>,
max_known_height: SlotHeight,
}
impl<T> OptimizedLMDGhost<T>
where
T: ClientDB + Sized,
{
pub fn new(
block_store: Arc<BeaconBlockStore<T>>,
state_store: Arc<BeaconStateStore<T>>,
) -> Self {
OptimizedLMDGhost {
cache: HashMap::new(),
ancestors: vec![HashMap::new(); 16],
latest_attestation_targets: HashMap::new(),
children: HashMap::new(),
max_known_height: SlotHeight::new(0),
block_store,
state_store,
}
}
/// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to
/// weighted votes.
pub fn get_latest_votes(
&self,
state_root: &Hash256,
block_slot: Slot,
spec: &ChainSpec,
) -> Result<HashMap<Hash256, u64>, ForkChoiceError> {
// get latest votes
// Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) //
// FORK_CHOICE_BALANCE_INCREMENT
// build a hashmap of block_hash to weighted votes
let mut latest_votes: HashMap<Hash256, u64> = HashMap::new();
// gets the current weighted votes
let current_state = self
.state_store
.get_deserialized(&state_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
let active_validator_indices =
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
for index in active_validator_indices {
let balance = std::cmp::min(
current_state.validator_balances[index],
spec.max_deposit_amount,
) / spec.fork_choice_balance_increment;
if balance > 0 {
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;
}
}
}
trace!("Latest votes: {:?}", latest_votes);
Ok(latest_votes)
}
/// Gets the ancestor at a given height `at_height` of a block specified by `block_hash`.
fn get_ancestor(
&mut self,
block_hash: Hash256,
target_height: SlotHeight,
spec: &ChainSpec,
) -> Option<Hash256> {
// return None if we can't get the block from the db.
let block_height = {
let block_slot = self
.block_store
.get_deserialized(&block_hash)
.ok()?
.expect("Should have returned already if None")
.slot;
block_slot.height(spec.genesis_slot)
};
// verify we haven't exceeded the block height
if target_height >= block_height {
if target_height > block_height {
return None;
} else {
return Some(block_hash);
}
}
// check if the result is stored in our cache
let cache_key = CacheKey::new(&block_hash, target_height.as_u64());
if let Some(ancestor) = self.cache.get(&cache_key) {
return Some(*ancestor);
}
// not in the cache recursively search for ancestors using a log-lookup
if let Some(ancestor) = {
let ancestor_lookup = self.ancestors
[log2_int((block_height - target_height - 1u64).as_u64()) as usize]
.get(&block_hash)
//TODO: Panic if we can't lookup and fork choice fails
.expect("All blocks should be added to the ancestor log lookup table");
self.get_ancestor(*ancestor_lookup, target_height, &spec)
} {
// add the result to the cache
self.cache.insert(cache_key, ancestor);
return Some(ancestor);
}
None
}
// looks for an obvious block winner given the latest votes for a specific height
fn get_clear_winner(
&mut self,
latest_votes: &HashMap<Hash256, u64>,
block_height: SlotHeight,
spec: &ChainSpec,
) -> Option<Hash256> {
// map of vote counts for every hash at this height
let mut current_votes: HashMap<Hash256, u64> = HashMap::new();
let mut total_vote_count = 0;
trace!("Clear winner at block height: {}", block_height);
// loop through the latest votes and count all votes
// these have already been weighted by balance
for (hash, votes) in latest_votes.iter() {
if let Some(ancestor) = self.get_ancestor(*hash, block_height, spec) {
let current_vote_value = current_votes.get(&ancestor).unwrap_or_else(|| &0);
current_votes.insert(ancestor, current_vote_value + *votes);
total_vote_count += votes;
}
}
// Check if there is a clear block winner at this height. If so return it.
for (hash, votes) in current_votes.iter() {
if *votes > total_vote_count / 2 {
// we have a clear winner, return it
return Some(*hash);
}
}
// didn't find a clear winner
None
}
// Finds the best child (one with highest votes)
fn choose_best_child(&self, votes: &HashMap<Hash256, u64>) -> Option<Hash256> {
if votes.is_empty() {
return None;
}
// Iterate through hashmap to get child with maximum votes
let best_child = votes.iter().max_by(|(child1, v1), (child2, v2)| {
let mut result = v1.cmp(v2);
// If votes are equal, choose smaller hash to break ties deterministically
if result == Ordering::Equal {
// Reverse so that max_by chooses smaller hash
result = child1.cmp(child2).reverse();
}
result
});
Some(*best_child.unwrap().0)
}
}
impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<T> {
fn add_block(
&mut self,
block: &BeaconBlock,
block_hash: &Hash256,
spec: &ChainSpec,
) -> Result<(), ForkChoiceError> {
// get the height of the parent
let parent_height = self
.block_store
.get_deserialized(&block.previous_block_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.previous_block_root))?
.slot
.height(spec.genesis_slot);
let parent_hash = &block.previous_block_root;
// add the new block to the children of parent
(*self
.children
.entry(block.previous_block_root)
.or_insert_with(|| vec![]))
.push(block_hash.clone());
// build the ancestor data structure
for index in 0..16 {
if parent_height % (1 << index) == 0 {
self.ancestors[index].insert(*block_hash, *parent_hash);
} else {
// TODO: This is unsafe. Will panic if parent_hash doesn't exist. Using it for debugging
let parent_ancestor = self.ancestors[index][parent_hash];
self.ancestors[index].insert(*block_hash, parent_ancestor);
}
}
// update the max height
self.max_known_height = std::cmp::max(self.max_known_height, parent_height + 1);
Ok(())
}
fn add_attestation(
&mut self,
validator_index: u64,
target_block_root: &Hash256,
spec: &ChainSpec,
) -> Result<(), ForkChoiceError> {
// simply add the attestation to the latest_attestation_target if the block_height is
// larger
trace!(
"Adding attestation of validator: {:?} for block: {}",
validator_index,
target_block_root
);
let attestation_target = self
.latest_attestation_targets
.entry(validator_index)
.or_insert_with(|| *target_block_root);
// if we already have a value
if attestation_target != target_block_root {
trace!("Old attestation found: {:?}", attestation_target);
// get the height of the target block
let block_height = self
.block_store
.get_deserialized(&target_block_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))?
.slot
.height(spec.genesis_slot);
// get the height of the past target block
let past_block_height = self
.block_store
.get_deserialized(&attestation_target)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))?
.slot
.height(spec.genesis_slot);
// update the attestation only if the new target is higher
if past_block_height < block_height {
trace!("Updating old attestation");
*attestation_target = *target_block_root;
}
}
Ok(())
}
/// Perform lmd_ghost on the current chain to find the head.
fn find_head(
&mut self,
justified_block_start: &Hash256,
spec: &ChainSpec,
) -> Result<Hash256, ForkChoiceError> {
debug!(
"Starting optimised fork choice at block: {}",
justified_block_start
);
let block = self
.block_store
.get_deserialized(&justified_block_start)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?;
let block_slot = block.slot;
let state_root = block.state_root;
let mut block_height = block_slot.height(spec.genesis_slot);
let mut current_head = *justified_block_start;
let mut latest_votes = self.get_latest_votes(&state_root, block_slot, spec)?;
// remove any votes that don't relate to our current head.
latest_votes
.retain(|hash, _| self.get_ancestor(*hash, block_height, spec) == Some(current_head));
// begin searching for the head
loop {
debug!(
"Iteration for block: {} with vote length: {}",
current_head,
latest_votes.len()
);
// if there are no children, we are done, return the current_head
let children = match self.children.get(&current_head) {
Some(children) => children.clone(),
None => {
debug!("Head found: {}", current_head);
return Ok(current_head);
}
};
// logarithmic lookup blocks to see if there are obvious winners, if so,
// progress to the next iteration.
let mut step =
power_of_2_below(self.max_known_height.saturating_sub(block_height).as_u64()) / 2;
while step > 0 {
trace!("Current Step: {}", step);
if let Some(clear_winner) = self.get_clear_winner(
&latest_votes,
block_height - (block_height % step) + step,
spec,
) {
current_head = clear_winner;
break;
}
step /= 2;
}
if step > 0 {
trace!("Found clear winner: {}", current_head);
}
// if our skip lookup failed and we only have one child, progress to that child
else if children.len() == 1 {
current_head = children[0];
trace!(
"Lookup failed, only one child, proceeding to child: {}",
current_head
);
}
// we need to find the best child path to progress down.
else {
trace!("Searching for best child");
let mut child_votes = HashMap::new();
for (voted_hash, vote) in latest_votes.iter() {
// if the latest votes correspond to a child
if let Some(child) = self.get_ancestor(*voted_hash, block_height + 1, spec) {
// add up the votes for each child
*child_votes.entry(child).or_insert_with(|| 0) += vote;
}
}
// check if we have votes of children, if not select the smallest hash child
if child_votes.is_empty() {
current_head = *children
.iter()
.min_by(|child1, child2| child1.cmp(child2))
.expect("Must be children here");
trace!(
"Children have no votes - smallest hash chosen: {}",
current_head
);
} else {
// given the votes on the children, find the best child
current_head = self
.choose_best_child(&child_votes)
.ok_or(ForkChoiceError::CannotFindBestChild)?;
trace!("Best child found: {}", current_head);
}
}
// didn't find head yet, proceed to next iteration
// update block height
block_height = self
.block_store
.get_deserialized(&current_head)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))?
.slot
.height(spec.genesis_slot);
// prune the latest votes for votes that are not part of current chosen chain
// more specifically, only keep votes that have head as an ancestor
for hash in latest_votes.keys() {
trace!(
"Ancestor for vote: {} at height: {} is: {:?}",
hash,
block_height,
self.get_ancestor(*hash, block_height, spec)
);
}
latest_votes.retain(|hash, _| {
self.get_ancestor(*hash, block_height, spec) == Some(current_head)
});
}
}
}
/// Type for storing blocks in a memory cache. Key is comprised of block-hash plus the height.
#[derive(PartialEq, Eq, Hash)]
pub struct CacheKey<T> {
block_hash: Hash256,
block_height: T,
}
impl<T> CacheKey<T> {
pub fn new(block_hash: &Hash256, block_height: T) -> Self {
CacheKey {
block_hash: *block_hash,
block_height,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_power_of_2_below() {
assert_eq!(power_of_2_below(4), 4);
assert_eq!(power_of_2_below(5), 4);
assert_eq!(power_of_2_below(7), 4);
assert_eq!(power_of_2_below(24), 16);
assert_eq!(power_of_2_below(32), 32);
assert_eq!(power_of_2_below(33), 32);
assert_eq!(power_of_2_below(63), 32);
}
#[test]
pub fn test_power_of_2_below_large() {
let pow: u64 = 1 << 24;
for x in (pow - 20)..(pow + 20) {
assert!(power_of_2_below(x) <= x, "{}", x);
}
}
}

View File

@ -8,10 +8,7 @@ use db::{
use log::{debug, trace};
use std::collections::HashMap;
use std::sync::Arc;
use types::{
readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock,
ChainSpec, Hash256, Slot,
};
use types::{BeaconBlock, ChainSpec, Hash256, Slot};
//TODO: Pruning and syncing
@ -62,10 +59,8 @@ where
.get_deserialized(&state_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
let active_validator_indices = get_active_validator_indices(
&current_state.validator_registry[..],
block_slot.epoch(spec.slots_per_epoch),
);
let active_validator_indices =
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
for index in active_validator_indices {
let balance = std::cmp::min(
@ -95,7 +90,7 @@ where
.block_store
.get_deserialized(&block_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*block_root))?
.slot();
.slot;
for (vote_hash, votes) in latest_votes.iter() {
let (root_at_slot, _) = self
@ -122,7 +117,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
// add the new block to the children of parent
(*self
.children
.entry(block.parent_root)
.entry(block.previous_block_root)
.or_insert_with(|| vec![]))
.push(block_hash.clone());
@ -155,7 +150,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
.block_store
.get_deserialized(&target_block_root)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))?
.slot()
.slot
.height(spec.genesis_slot);
// get the height of the past target block
@ -163,7 +158,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
.block_store
.get_deserialized(&attestation_target)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))?
.slot()
.slot
.height(spec.genesis_slot);
// update the attestation only if the new target is higher
if past_block_height < block_height {
@ -186,9 +181,9 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
.get_deserialized(&justified_block_start)?
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?;
let start_state_root = start.state_root();
let start_state_root = start.state_root;
let latest_votes = self.get_latest_votes(&start_state_root, start.slot(), spec)?;
let latest_votes = self.get_latest_votes(&start_state_root, start.slot, spec)?;
let mut head_hash = *justified_block_start;
@ -210,6 +205,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
trace!("Children found: {:?}", children);
let mut head_vote_count = 0;
head_hash = children[0];
for child_hash in children {
let vote_count = self.get_vote_count(&latest_votes, &child_hash)?;
trace!("Vote count for child: {} is: {}", child_hash, vote_count);
@ -218,6 +214,10 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
head_hash = *child_hash;
head_vote_count = vote_count;
}
// resolve ties - choose smaller hash
else if vote_count == head_vote_count && *child_hash < head_hash {
head_hash = *child_hash;
}
}
}
Ok(head_hash)

View File

@ -63,3 +63,82 @@ test_cases:
- b7: 2
heads:
- id: 'b4'
- blocks:
- id: 'b0'
parent: 'b0'
- id: 'b1'
parent: 'b0'
- id: 'b2'
parent: 'b0'
- id: 'b3'
parent: 'b0'
- id: 'b4'
parent: 'b1'
- id: 'b5'
parent: 'b1'
- id: 'b6'
parent: 'b2'
- id: 'b7'
parent: 'b2'
- id: 'b8'
parent: 'b3'
- id: 'b9'
parent: 'b3'
weights:
- b1: 2
- b2: 1
- b3: 1
- b4: 7
- b5: 5
- b6: 2
- b7: 4
- b8: 4
- b9: 2
heads:
- id: 'b4'
- blocks:
- id: 'b0'
parent: 'b0'
- id: 'b1'
parent: 'b0'
- id: 'b2'
parent: 'b0'
- id: 'b3'
parent: 'b0'
- id: 'b4'
parent: 'b1'
- id: 'b5'
parent: 'b1'
- id: 'b6'
parent: 'b2'
- id: 'b7'
parent: 'b2'
- id: 'b8'
parent: 'b3'
- id: 'b9'
parent: 'b3'
weights:
- b1: 1
- b2: 1
- b3: 1
- b4: 7
- b5: 5
- b6: 2
- b7: 4
- b8: 4
- b9: 2
heads:
- id: 'b7'
- blocks:
- id: 'b0'
parent: 'b0'
- id: 'b1'
parent: 'b0'
- id: 'b2'
parent: 'b0'
weights:
- b1: 0
- b2: 0
heads:
- id: 'b1'

View File

@ -35,3 +35,31 @@ test_cases:
- b3: 3
heads:
- id: 'b1'
# equal weights children. Should choose lower hash b2
- blocks:
- id: 'b0'
parent: 'b0'
- id: 'b1'
parent: 'b0'
- id: 'b2'
parent: 'b0'
- id: 'b3'
parent: 'b0'
weights:
- b1: 5
- b2: 6
- b3: 6
heads:
- id: 'b2'
- blocks:
- id: 'b0'
parent: 'b0'
- id: 'b1'
parent: 'b0'
- id: 'b2'
parent: 'b0'
weights:
- b1: 0
- b2: 0
heads:
- id: 'b1'

View File

@ -3,7 +3,7 @@
extern crate beacon_chain;
extern crate bls;
extern crate db;
//extern crate env_logger; // for debugging
// extern crate env_logger; // for debugging
extern crate fork_choice;
extern crate hex;
extern crate log;
@ -15,18 +15,32 @@ pub use beacon_chain::BeaconChain;
use bls::Signature;
use db::stores::{BeaconBlockStore, BeaconStateStore};
use db::MemoryDB;
//use env_logger::{Builder, Env};
use fork_choice::{BitwiseLMDGhost, ForkChoice, ForkChoiceAlgorithm, LongestChain, SlowLMDGhost};
// use env_logger::{Builder, Env};
use fork_choice::{
BitwiseLMDGhost, ForkChoice, ForkChoiceAlgorithm, LongestChain, OptimizedLMDGhost, SlowLMDGhost,
};
use ssz::ssz_encode;
use std::collections::HashMap;
use std::sync::Arc;
use std::{fs::File, io::prelude::*, path::PathBuf};
use types::test_utils::TestingBeaconStateBuilder;
use types::{BeaconBlock, BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Slot};
use types::{BeaconBlock, BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Keypair, Slot};
use yaml_rust::yaml;
// Note: We Assume the block Id's are hex-encoded.
#[test]
fn test_optimized_lmd_ghost() {
// set up logging
// Builder::from_env(Env::default().default_filter_or("trace")).init();
test_yaml_vectors(
ForkChoiceAlgorithm::OptimizedLMDGhost,
"tests/lmd_ghost_test_vectors.yaml",
100,
);
}
#[test]
fn test_bitwise_lmd_ghost() {
// set up logging
@ -76,6 +90,8 @@ fn test_yaml_vectors(
let randao_reveal = Signature::empty_signature();
let signature = Signature::empty_signature();
let body = BeaconBlockBody {
eth1_data,
randao_reveal,
proposer_slashings: vec![],
attester_slashings: vec![],
attestations: vec![],
@ -103,14 +119,14 @@ fn test_yaml_vectors(
// default params for genesis
let block_hash = id_to_hash(&block_id);
let mut slot = spec.genesis_slot;
let parent_root = id_to_hash(&parent_id);
let previous_block_root = id_to_hash(&parent_id);
// set the slot and parent based off the YAML. Start with genesis;
// if not the genesis, update slot
if parent_id != block_id {
// find parent slot
slot = *(block_slot
.get(&parent_root)
.get(&previous_block_root)
.expect("Parent should have a slot number"))
+ 1;
} else {
@ -123,10 +139,8 @@ fn test_yaml_vectors(
// build the BeaconBlock
let beacon_block = BeaconBlock {
slot,
parent_root,
previous_block_root,
state_root: state_root.clone(),
randao_reveal: randao_reveal.clone(),
eth1_data: eth1_data.clone(),
signature: signature.clone(),
body: body.clone(),
};
@ -204,7 +218,7 @@ fn load_test_cases_from_yaml(file_path: &str) -> Vec<yaml_rust::Yaml> {
// initialise a single validator and state. All blocks will reference this state root.
fn setup_inital_state(
fork_choice_algo: &ForkChoiceAlgorithm,
no_validators: usize,
num_validators: usize,
) -> (Box<ForkChoice>, Arc<BeaconBlockStore<MemoryDB>>, Hash256) {
let db = Arc::new(MemoryDB::open());
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
@ -212,6 +226,10 @@ fn setup_inital_state(
// the fork choice instantiation
let fork_choice: Box<ForkChoice> = match fork_choice_algo {
ForkChoiceAlgorithm::OptimizedLMDGhost => Box::new(OptimizedLMDGhost::new(
block_store.clone(),
state_store.clone(),
)),
ForkChoiceAlgorithm::BitwiseLMDGhost => Box::new(BitwiseLMDGhost::new(
block_store.clone(),
state_store.clone(),
@ -224,8 +242,9 @@ fn setup_inital_state(
let spec = ChainSpec::foundation();
let state_builder =
TestingBeaconStateBuilder::from_deterministic_keypairs(no_validators, &spec);
let mut state_builder =
TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec);
state_builder.build_caches(&spec).unwrap();
let (state, _keypairs) = state_builder.build();
let state_root = state.canonical_root();

View File

@ -11,6 +11,9 @@ harness = false
[dev-dependencies]
criterion = "0.2"
env_logger = "0.6.0"
serde = "1.0"
serde_derive = "1.0"
serde_yaml = "0.8"
[dependencies]
bls = { path = "../utils/bls" }

View File

@ -1,6 +1,5 @@
use criterion::Criterion;
use criterion::{black_box, Benchmark};
use log::debug;
use ssz::TreeHash;
use state_processing::{
per_block_processing,
@ -10,195 +9,12 @@ use state_processing::{
verify_block_signature,
},
};
use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder};
use types::*;
/// Run the benchmarking suite on a foundation spec with 16,384 validators.
pub fn bench_block_processing_n_validators(c: &mut Criterion, validator_count: usize) {
let spec = ChainSpec::foundation();
let (mut state, keypairs) = build_state(validator_count, &spec);
let block = build_block(&mut state, &keypairs, &spec);
assert_eq!(
block.body.proposer_slashings.len(),
spec.max_proposer_slashings as usize,
"The block should have the maximum possible proposer slashings"
);
assert_eq!(
block.body.attester_slashings.len(),
spec.max_attester_slashings as usize,
"The block should have the maximum possible attester slashings"
);
for attester_slashing in &block.body.attester_slashings {
let len_1 = attester_slashing
.slashable_attestation_1
.validator_indices
.len();
let len_2 = attester_slashing
.slashable_attestation_1
.validator_indices
.len();
assert!(
(len_1 == len_2) && (len_2 == spec.max_indices_per_slashable_vote as usize),
"Each attester slashing should have the maximum possible validator indices"
);
}
assert_eq!(
block.body.attestations.len(),
spec.max_attestations as usize,
"The block should have the maximum possible attestations."
);
assert_eq!(
block.body.deposits.len(),
spec.max_deposits as usize,
"The block should have the maximum possible deposits."
);
assert_eq!(
block.body.voluntary_exits.len(),
spec.max_voluntary_exits as usize,
"The block should have the maximum possible voluntary exits."
);
assert_eq!(
block.body.transfers.len(),
spec.max_transfers as usize,
"The block should have the maximum possible transfers."
);
bench_block_processing(
c,
&block,
&state,
&spec,
&format!("{}_validators", validator_count),
);
}
fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec<Keypair>) {
let mut builder =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
// Set the state to be just before an epoch transition.
let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch);
builder.teleport_to_slot(target_slot, &spec);
// Builds all caches; benches will not contain shuffling/committee building times.
builder.build_caches(&spec).unwrap();
builder.build()
}
fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> BeaconBlock {
let mut builder = TestingBeaconBlockBuilder::new(spec);
builder.set_slot(state.slot);
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
let keypair = &keypairs[proposer_index];
builder.set_randao_reveal(&keypair.sk, &state.fork, spec);
// Used as a stream of validator indices for use in slashings, exits, etc.
let mut validators_iter = (0..keypairs.len() as u64).into_iter();
// Insert the maximum possible number of `ProposerSlashing` objects.
debug!(
"Inserting {} proposer slashings...",
spec.max_proposer_slashings
);
for _ in 0..spec.max_proposer_slashings {
let validator_index = validators_iter.next().expect("Insufficient validators.");
builder.insert_proposer_slashing(
validator_index,
&keypairs[validator_index as usize].sk,
&state.fork,
spec,
);
}
// Insert the maximum possible number of `AttesterSlashing` objects
debug!(
"Inserting {} attester slashings...",
spec.max_attester_slashings
);
for _ in 0..spec.max_attester_slashings {
let mut attesters: Vec<u64> = vec![];
let mut secret_keys: Vec<&SecretKey> = vec![];
for _ in 0..spec.max_indices_per_slashable_vote {
let validator_index = validators_iter.next().expect("Insufficient validators.");
attesters.push(validator_index);
secret_keys.push(&keypairs[validator_index as usize].sk);
}
builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec);
}
// Insert the maximum possible number of `Attestation` objects.
debug!("Inserting {} attestations...", spec.max_attestations);
let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect();
builder
.fill_with_attestations(state, &all_secret_keys, spec)
.unwrap();
// Insert the maximum possible number of `Deposit` objects.
debug!("Inserting {} deposits...", spec.max_deposits);
for i in 0..spec.max_deposits {
builder.insert_deposit(32_000_000_000, state.deposit_index + i, state, spec);
}
// Insert the maximum possible number of `Exit` objects.
debug!("Inserting {} exits...", spec.max_voluntary_exits);
for _ in 0..spec.max_voluntary_exits {
let validator_index = validators_iter.next().expect("Insufficient validators.");
builder.insert_exit(
state,
validator_index,
&keypairs[validator_index as usize].sk,
spec,
);
}
// Insert the maximum possible number of `Transfer` objects.
debug!("Inserting {} transfers...", spec.max_transfers);
for _ in 0..spec.max_transfers {
let validator_index = validators_iter.next().expect("Insufficient validators.");
// Manually set the validator to be withdrawn.
state.validator_registry[validator_index as usize].withdrawable_epoch =
state.previous_epoch(spec);
builder.insert_transfer(
state,
validator_index,
validator_index,
1,
keypairs[validator_index as usize].clone(),
spec,
);
}
let mut block = builder.build(&keypair.sk, &state.fork, spec);
// Set the eth1 data to be different from the state.
block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]);
block
}
/// Run the detailed benchmarking suite on the given `BeaconState`.
///
/// `desc` will be added to the title of each bench.
fn bench_block_processing(
pub fn bench_block_processing(
c: &mut Criterion,
initial_block: &BeaconBlock,
initial_state: &BeaconState,
@ -426,6 +242,23 @@ fn bench_block_processing(
.sample_size(10),
);
let mut state = initial_state.clone();
state.drop_pubkey_cache();
c.bench(
&format!("{}/block_processing", desc),
Benchmark::new("build_pubkey_cache", move |b| {
b.iter_batched(
|| state.clone(),
|mut state| {
state.update_pubkey_cache().unwrap();
state
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(10),
);
let block = initial_block.clone();
c.bench(
&format!("{}/block_processing", desc),

View File

@ -4,14 +4,13 @@ use ssz::TreeHash;
use state_processing::{
per_epoch_processing,
per_epoch_processing::{
calculate_active_validator_indices, calculate_attester_sets, clean_attestations,
process_crosslinks, process_eth1_data, process_justification,
process_rewards_and_penalities, process_validator_registry, update_active_tree_index_roots,
update_latest_slashed_balances,
clean_attestations, initialize_validator_statuses, process_crosslinks, process_eth1_data,
process_justification, process_rewards_and_penalities, process_validator_registry,
update_active_tree_index_roots, update_latest_slashed_balances,
},
};
use types::test_utils::TestingBeaconStateBuilder;
use types::{validator_registry::get_active_validator_indices, *};
use types::*;
pub const BENCHING_SAMPLE_SIZE: usize = 10;
pub const SMALL_BENCHING_SAMPLE_SIZE: usize = 10;
@ -49,16 +48,6 @@ pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: u
"The state should have an attestation for each committee."
);
// Assert that each attestation in the state has full participation.
let committee_size = validator_count / committees_per_epoch as usize;
for a in &state.latest_attestations {
assert_eq!(
a.aggregation_bitfield.num_set_bits(),
committee_size,
"Each attestation in the state should have full participation"
);
}
// Assert that we will run the first arm of process_rewards_and_penalities
let epochs_since_finality = state.next_epoch(&spec) - state.finalized_epoch;
assert_eq!(
@ -73,64 +62,6 @@ pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: u
///
/// `desc` will be added to the title of each bench.
fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSpec, desc: &str) {
let state_clone = state.clone();
let spec_clone = spec.clone();
c.bench(
&format!("{}/epoch_processing", desc),
Benchmark::new("calculate_active_validator_indices", move |b| {
b.iter_batched(
|| state_clone.clone(),
|mut state| {
calculate_active_validator_indices(&mut state, &spec_clone);
state
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(BENCHING_SAMPLE_SIZE),
);
let state_clone = state.clone();
let spec_clone = spec.clone();
let active_validator_indices = calculate_active_validator_indices(&state, &spec);
c.bench(
&format!("{}/epoch_processing", desc),
Benchmark::new("calculate_current_total_balance", move |b| {
b.iter_batched(
|| state_clone.clone(),
|state| {
state.get_total_balance(&active_validator_indices[..], &spec_clone);
state
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(BENCHING_SAMPLE_SIZE),
);
let state_clone = state.clone();
let spec_clone = spec.clone();
c.bench(
&format!("{}/epoch_processing", desc),
Benchmark::new("calculate_previous_total_balance", move |b| {
b.iter_batched(
|| state_clone.clone(),
|state| {
state.get_total_balance(
&get_active_validator_indices(
&state.validator_registry,
state.previous_epoch(&spec_clone),
)[..],
&spec_clone,
);
state
},
criterion::BatchSize::SmallInput,
)
})
.sample_size(BENCHING_SAMPLE_SIZE),
);
let state_clone = state.clone();
let spec_clone = spec.clone();
c.bench(
@ -152,11 +83,11 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp
let spec_clone = spec.clone();
c.bench(
&format!("{}/epoch_processing", desc),
Benchmark::new("calculate_attester_sets", move |b| {
Benchmark::new("initialize_validator_statuses", move |b| {
b.iter_batched(
|| state_clone.clone(),
|mut state| {
calculate_attester_sets(&mut state, &spec_clone).unwrap();
initialize_validator_statuses(&mut state, &spec_clone).unwrap();
state
},
criterion::BatchSize::SmallInput,
@ -167,28 +98,14 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp
let state_clone = state.clone();
let spec_clone = spec.clone();
let previous_epoch = state.previous_epoch(&spec);
let attesters = calculate_attester_sets(&state, &spec).unwrap();
let active_validator_indices = calculate_active_validator_indices(&state, &spec);
let current_total_balance = state.get_total_balance(&active_validator_indices[..], &spec);
let previous_total_balance = state.get_total_balance(
&get_active_validator_indices(&state.validator_registry, previous_epoch)[..],
&spec,
);
let attesters = initialize_validator_statuses(&state, &spec).unwrap();
c.bench(
&format!("{}/epoch_processing", desc),
Benchmark::new("process_justification", move |b| {
b.iter_batched(
|| state_clone.clone(),
|mut state| {
process_justification(
&mut state,
current_total_balance,
previous_total_balance,
attesters.previous_epoch_boundary.balance,
attesters.current_epoch_boundary.balance,
&spec_clone,
);
process_justification(&mut state, &attesters.total_balances, &spec_clone);
state
},
criterion::BatchSize::SmallInput,
@ -213,25 +130,17 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp
let mut state_clone = state.clone();
let spec_clone = spec.clone();
let previous_epoch = state.previous_epoch(&spec);
let attesters = calculate_attester_sets(&state, &spec).unwrap();
let active_validator_indices = calculate_active_validator_indices(&state, &spec);
let previous_total_balance = state.get_total_balance(
&get_active_validator_indices(&state.validator_registry, previous_epoch)[..],
&spec,
);
let attesters = initialize_validator_statuses(&state, &spec).unwrap();
let winning_root_for_shards = process_crosslinks(&mut state_clone, &spec).unwrap();
c.bench(
&format!("{}/epoch_processing", desc),
Benchmark::new("process_rewards_and_penalties", move |b| {
b.iter_batched(
|| state_clone.clone(),
|mut state| {
|| (state_clone.clone(), attesters.clone()),
|(mut state, mut attesters)| {
process_rewards_and_penalities(
&mut state,
&active_validator_indices,
&attesters,
previous_total_balance,
&mut attesters,
&winning_root_for_shards,
&spec_clone,
)
@ -261,32 +170,8 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp
.sample_size(BENCHING_SAMPLE_SIZE),
);
let mut state_clone = state.clone();
let state_clone = state.clone();
let spec_clone = spec.clone();
let previous_epoch = state.previous_epoch(&spec);
let attesters = calculate_attester_sets(&state, &spec).unwrap();
let active_validator_indices = calculate_active_validator_indices(&state, &spec);
let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec);
let previous_total_balance = state.get_total_balance(
&get_active_validator_indices(&state.validator_registry, previous_epoch)[..],
&spec,
);
assert_eq!(
state_clone.finalized_epoch, state_clone.validator_registry_update_epoch,
"The last registry update should be at the last finalized epoch."
);
process_justification(
&mut state_clone,
current_total_balance,
previous_total_balance,
attesters.previous_epoch_boundary.balance,
attesters.current_epoch_boundary.balance,
spec,
);
assert!(
state_clone.finalized_epoch > state_clone.validator_registry_update_epoch,
"The state should have been finalized."
);
c.bench(
&format!("{}/epoch_processing", desc),
Benchmark::new("process_validator_registry", move |b| {

View File

@ -1,26 +1,103 @@
use criterion::Benchmark;
use block_benching_builder::BlockBenchingBuilder;
use criterion::Criterion;
use criterion::{criterion_group, criterion_main};
use env_logger::{Builder, Env};
use types::test_utils::TestingBeaconStateBuilder;
use log::info;
use types::*;
mod bench_block_processing;
mod bench_epoch_processing;
mod block_benching_builder;
pub const VALIDATOR_COUNT: usize = 300_032;
pub const VALIDATOR_COUNT: usize = 16_384;
// `LOG_LEVEL == "debug"` gives logs, but they're very noisy and slow down benching.
pub const LOG_LEVEL: &str = "";
// `LOG_LEVEL == "info"` gives handy messages.
pub const LOG_LEVEL: &str = "info";
pub fn state_processing(c: &mut Criterion) {
/// Build a worst-case block and benchmark processing it.
pub fn block_processing_worst_case(c: &mut Criterion) {
if LOG_LEVEL != "" {
Builder::from_env(Env::default().default_filter_or(LOG_LEVEL)).init();
}
info!(
"Building worst case block bench with {} validators",
VALIDATOR_COUNT
);
bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT);
// Use the specifications from the Eth2.0 spec.
let spec = ChainSpec::foundation();
// Create a builder for configuring the block and state for benching.
let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec);
// Set the number of included operations to be maximum (e.g., `MAX_ATTESTATIONS`, etc.)
bench_builder.maximize_block_operations(&spec);
// Set the state and block to be in the last slot of the 4th epoch.
let last_slot_of_epoch = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch);
bench_builder.set_slot(last_slot_of_epoch, &spec);
// Build all the state caches so the build times aren't included in the benches.
bench_builder.build_caches(&spec);
// Generate the block and state then run benches.
let (block, state) = bench_builder.build(&spec);
bench_block_processing::bench_block_processing(
c,
&block,
&state,
&spec,
&format!("{}_validators/worst_case", VALIDATOR_COUNT),
);
}
/// Build a reasonable-case block and benchmark processing it.
pub fn block_processing_reasonable_case(c: &mut Criterion) {
info!(
"Building reasonable case block bench with {} validators",
VALIDATOR_COUNT
);
// Use the specifications from the Eth2.0 spec.
let spec = ChainSpec::foundation();
// Create a builder for configuring the block and state for benching.
let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec);
// Set the number of included operations to what we might expect normally.
bench_builder.num_proposer_slashings = 0;
bench_builder.num_attester_slashings = 0;
bench_builder.num_attestations = (spec.shard_count / spec.slots_per_epoch) as usize;
bench_builder.num_deposits = 2;
bench_builder.num_exits = 2;
bench_builder.num_transfers = 2;
// Set the state and block to be in the last slot of the 4th epoch.
let last_slot_of_epoch = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch);
bench_builder.set_slot(last_slot_of_epoch, &spec);
// Build all the state caches so the build times aren't included in the benches.
bench_builder.build_caches(&spec);
// Generate the block and state then run benches.
let (block, state) = bench_builder.build(&spec);
bench_block_processing::bench_block_processing(
c,
&block,
&state,
&spec,
&format!("{}_validators/reasonable_case", VALIDATOR_COUNT),
);
}
pub fn state_processing(c: &mut Criterion) {
bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT);
}
criterion_group!(benches, state_processing);
criterion_group!(
benches,
block_processing_reasonable_case,
block_processing_worst_case,
state_processing
);
criterion_main!(benches);

View File

@ -0,0 +1,175 @@
use log::info;
use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder};
use types::*;
pub struct BlockBenchingBuilder {
pub state_builder: TestingBeaconStateBuilder,
pub block_builder: TestingBeaconBlockBuilder,
pub num_validators: usize,
pub num_proposer_slashings: usize,
pub num_attester_slashings: usize,
pub num_indices_per_slashable_vote: usize,
pub num_attestations: usize,
pub num_deposits: usize,
pub num_exits: usize,
pub num_transfers: usize,
}
impl BlockBenchingBuilder {
pub fn new(num_validators: usize, spec: &ChainSpec) -> Self {
let state_builder =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec);
let block_builder = TestingBeaconBlockBuilder::new(spec);
Self {
state_builder,
block_builder,
num_validators: 0,
num_proposer_slashings: 0,
num_attester_slashings: 0,
num_indices_per_slashable_vote: spec.max_indices_per_slashable_vote as usize,
num_attestations: 0,
num_deposits: 0,
num_exits: 0,
num_transfers: 0,
}
}
pub fn maximize_block_operations(&mut self, spec: &ChainSpec) {
self.num_proposer_slashings = spec.max_proposer_slashings as usize;
self.num_attester_slashings = spec.max_attester_slashings as usize;
self.num_indices_per_slashable_vote = spec.max_indices_per_slashable_vote as usize;
self.num_attestations = spec.max_attestations as usize;
self.num_deposits = spec.max_deposits as usize;
self.num_exits = spec.max_voluntary_exits as usize;
self.num_transfers = spec.max_transfers as usize;
}
pub fn set_slot(&mut self, slot: Slot, spec: &ChainSpec) {
self.state_builder.teleport_to_slot(slot, &spec);
}
pub fn build_caches(&mut self, spec: &ChainSpec) {
// Builds all caches; benches will not contain shuffling/committee building times.
self.state_builder.build_caches(&spec).unwrap();
}
pub fn build(mut self, spec: &ChainSpec) -> (BeaconBlock, BeaconState) {
let (mut state, keypairs) = self.state_builder.build();
let builder = &mut self.block_builder;
builder.set_slot(state.slot);
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
let keypair = &keypairs[proposer_index];
builder.set_randao_reveal(&keypair.sk, &state.fork, spec);
// Used as a stream of validator indices for use in slashings, exits, etc.
let mut validators_iter = (0..keypairs.len() as u64).into_iter();
// Insert `ProposerSlashing` objects.
for _ in 0..self.num_proposer_slashings {
let validator_index = validators_iter.next().expect("Insufficient validators.");
builder.insert_proposer_slashing(
validator_index,
&keypairs[validator_index as usize].sk,
&state.fork,
spec,
);
}
info!(
"Inserted {} proposer slashings.",
builder.block.body.proposer_slashings.len()
);
// Insert `AttesterSlashing` objects
for _ in 0..self.num_attester_slashings {
let mut attesters: Vec<u64> = vec![];
let mut secret_keys: Vec<&SecretKey> = vec![];
for _ in 0..self.num_indices_per_slashable_vote {
let validator_index = validators_iter.next().expect("Insufficient validators.");
attesters.push(validator_index);
secret_keys.push(&keypairs[validator_index as usize].sk);
}
builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec);
}
info!(
"Inserted {} attester slashings.",
builder.block.body.attester_slashings.len()
);
// Insert `Attestation` objects.
let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect();
builder
.insert_attestations(
&state,
&all_secret_keys,
self.num_attestations as usize,
spec,
)
.unwrap();
info!(
"Inserted {} attestations.",
builder.block.body.attestations.len()
);
// Insert `Deposit` objects.
for i in 0..self.num_deposits {
builder.insert_deposit(
32_000_000_000,
state.deposit_index + (i as u64),
&state,
spec,
);
}
info!("Inserted {} deposits.", builder.block.body.deposits.len());
// Insert the maximum possible number of `Exit` objects.
for _ in 0..self.num_exits {
let validator_index = validators_iter.next().expect("Insufficient validators.");
builder.insert_exit(
&state,
validator_index,
&keypairs[validator_index as usize].sk,
spec,
);
}
info!(
"Inserted {} exits.",
builder.block.body.voluntary_exits.len()
);
// Insert the maximum possible number of `Transfer` objects.
for _ in 0..self.num_transfers {
let validator_index = validators_iter.next().expect("Insufficient validators.");
// Manually set the validator to be withdrawn.
state.validator_registry[validator_index as usize].withdrawable_epoch =
state.previous_epoch(spec);
builder.insert_transfer(
&state,
validator_index,
validator_index,
1,
keypairs[validator_index as usize].clone(),
spec,
);
}
info!("Inserted {} transfers.", builder.block.body.transfers.len());
let mut block = self.block_builder.build(&keypair.sk, &state.fork, spec);
// Set the eth1 data to be different from the state.
block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]);
(block, state)
}
}

View File

@ -0,0 +1,347 @@
title: Sanity tests
summary: Basic sanity checks from phase 0 spec pythonization. All tests are run with
`verify_signatures` as set to False.
test_suite: beacon_state
fork: tchaikovsky
version: v0.5.0
test_cases:
- name: test_empty_block_transition
config: {SHARD_COUNT: 8, TARGET_COMMITTEE_SIZE: 4, MAX_BALANCE_CHURN_QUOTIENT: 32,
MAX_INDICES_PER_SLASHABLE_VOTE: 4096, MAX_EXIT_DEQUEUES_PER_EPOCH: 4, SHUFFLE_ROUND_COUNT: 90,
DEPOSIT_CONTRACT_TREE_DEPTH: 32, MIN_DEPOSIT_AMOUNT: 1000000000, MAX_DEPOSIT_AMOUNT: 32000000000,
FORK_CHOICE_BALANCE_INCREMENT: 1000000000, EJECTION_BALANCE: 16000000000, GENESIS_FORK_VERSION: 0,
GENESIS_SLOT: 4294967296, GENESIS_EPOCH: 536870912, GENESIS_START_SHARD: 0, BLS_WITHDRAWAL_PREFIX_BYTE: 0,
SECONDS_PER_SLOT: 6, MIN_ATTESTATION_INCLUSION_DELAY: 2, SLOTS_PER_EPOCH: 8, MIN_SEED_LOOKAHEAD: 1,
ACTIVATION_EXIT_DELAY: 4, EPOCHS_PER_ETH1_VOTING_PERIOD: 16, SLOTS_PER_HISTORICAL_ROOT: 64,
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256, PERSISTENT_COMMITTEE_PERIOD: 2048, LATEST_RANDAO_MIXES_LENGTH: 64,
LATEST_ACTIVE_INDEX_ROOTS_LENGTH: 64, LATEST_SLASHED_EXIT_LENGTH: 64, BASE_REWARD_QUOTIENT: 32,
WHISTLEBLOWER_REWARD_QUOTIENT: 512, ATTESTATION_INCLUSION_REWARD_QUOTIENT: 8,
INACTIVITY_PENALTY_QUOTIENT: 16777216, MIN_PENALTY_QUOTIENT: 32, MAX_PROPOSER_SLASHINGS: 16,
MAX_ATTESTER_SLASHINGS: 1, MAX_ATTESTATIONS: 128, MAX_DEPOSITS: 16, MAX_VOLUNTARY_EXITS: 16,
MAX_TRANSFERS: 16, DOMAIN_BEACON_BLOCK: 0, DOMAIN_RANDAO: 1, DOMAIN_ATTESTATION: 2,
DOMAIN_DEPOSIT: 3, DOMAIN_VOLUNTARY_EXIT: 4, DOMAIN_TRANSFER: 5}
verify_signatures: false
initial_state:
slot: 4294967296
genesis_time: 0
fork: {previous_version: 0, current_version: 0, epoch: 536870912}
validator_registry:
- {pubkey: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x0c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x0d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x1a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x1d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x1e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
- {pubkey: '0x1f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222',
activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615,
initiated_exit: false, slashed: false}
validator_balances: [32000000000, 32000000000, 32000000000, 32000000000, 32000000000,
32000000000, 32000000000, 32000000000, 32000000000, 32000000000, 32000000000,
32000000000, 32000000000, 32000000000, 32000000000, 32000000000, 32000000000,
32000000000, 32000000000, 32000000000, 32000000000, 32000000000, 32000000000,
32000000000, 32000000000, 32000000000, 32000000000, 32000000000, 32000000000,
32000000000, 32000000000, 32000000000]
validator_registry_update_epoch: 536870912
latest_randao_mixes: ['0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000']
previous_shuffling_start_shard: 0
current_shuffling_start_shard: 0
previous_shuffling_epoch: 536870912
current_shuffling_epoch: 536870912
previous_shuffling_seed: '0x0000000000000000000000000000000000000000000000000000000000000000'
current_shuffling_seed: '0x94ab448e948e6d501a2b48c1e9a0946f871100969f6fa70a990acf2348c9b185'
previous_epoch_attestations: []
current_epoch_attestations: []
previous_justified_epoch: 536870912
current_justified_epoch: 536870912
previous_justified_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
current_justified_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
justification_bitfield: 0
finalized_epoch: 536870912
finalized_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
latest_crosslinks:
- {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'}
- {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'}
- {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'}
- {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'}
- {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'}
- {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'}
- {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'}
- {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'}
latest_block_roots: ['0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000']
latest_state_roots: ['0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000']
latest_active_index_roots: ['0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42',
'0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42']
latest_slashed_balances: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
latest_block_header: {slot: 4294967296, previous_block_root: '0x0000000000000000000000000000000000000000000000000000000000000000',
state_root: '0x0000000000000000000000000000000000000000000000000000000000000000',
block_body_root: '0x5359b62990beb1d78e1cec479f5a4d80af84709886a8e16c535dff0556dc0e2d',
signature: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'}
historical_roots: []
latest_eth1_data: {deposit_root: '0xb05de6a9059df0c9a2ab5f76708d256941dfe9eb89e6fda549b30713087d2a5e',
block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000'}
eth1_data_votes: []
deposit_index: 32
blocks:
- slot: 4294967297
previous_block_root: '0x92ed652508d2b4c109a857107101716b18e257e7ce0d199d4b16232956e9e27e'
state_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
body:
randao_reveal: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
eth1_data: {deposit_root: '0x0000000000000000000000000000000000000000000000000000000000000000',
block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000'}
proposer_slashings: []
attester_slashings: []
attestations: []
deposits: []
voluntary_exits: []
transfers: []
signature: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
expected_state: {slot: 4294967297}

View File

@ -0,0 +1,22 @@
use types::{BeaconStateError as Error, *};
/// Exit the validator of the given `index`.
///
/// Spec v0.5.0
pub fn exit_validator(
state: &mut BeaconState,
validator_index: usize,
spec: &ChainSpec,
) -> Result<(), Error> {
if validator_index >= state.validator_registry.len() {
return Err(Error::UnknownValidator);
}
let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec);
if state.validator_registry[validator_index].exit_epoch > delayed_epoch {
state.validator_registry[validator_index].exit_epoch = delayed_epoch;
}
Ok(())
}

View File

@ -0,0 +1,7 @@
mod exit_validator;
mod slash_validator;
mod verify_bitfield;
pub use exit_validator::exit_validator;
pub use slash_validator::slash_validator;
pub use verify_bitfield::verify_bitfield_length;

View File

@ -0,0 +1,62 @@
use crate::common::exit_validator;
use types::{BeaconStateError as Error, *};
/// Slash the validator with index ``index``.
///
/// Spec v0.5.0
pub fn slash_validator(
state: &mut BeaconState,
validator_index: usize,
spec: &ChainSpec,
) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec);
if (validator_index >= state.validator_registry.len())
| (validator_index >= state.validator_balances.len())
{
return Err(BeaconStateError::UnknownValidator);
}
let validator = &state.validator_registry[validator_index];
let effective_balance = state.get_effective_balance(validator_index, spec)?;
// A validator that is withdrawn cannot be slashed.
//
// This constraint will be lifted in Phase 0.
if state.slot
>= validator
.withdrawable_epoch
.start_slot(spec.slots_per_epoch)
{
return Err(Error::ValidatorIsWithdrawable);
}
exit_validator(state, validator_index, spec)?;
state.set_slashed_balance(
current_epoch,
state.get_slashed_balance(current_epoch, spec)? + effective_balance,
spec,
)?;
let whistleblower_index =
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient;
safe_add_assign!(
state.validator_balances[whistleblower_index as usize],
whistleblower_reward
);
safe_sub_assign!(
state.validator_balances[validator_index],
whistleblower_reward
);
state.validator_registry[validator_index].slashed = true;
state.validator_registry[validator_index].withdrawable_epoch =
current_epoch + Epoch::from(spec.latest_slashed_exit_length);
Ok(())
}

View File

@ -1,10 +1,10 @@
use crate::*;
use types::*;
/// Verify ``bitfield`` against the ``committee_size``.
///
/// Is title `verify_bitfield` in spec.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_bitfield_length(bitfield: &Bitfield, committee_size: usize) -> bool {
if bitfield.num_bytes() != ((committee_size + 7) / 8) {
return false;

View File

@ -0,0 +1,58 @@
use super::per_block_processing::{errors::BlockProcessingError, process_deposits};
use ssz::TreeHash;
use types::*;
pub enum GenesisError {
BlockProcessingError(BlockProcessingError),
BeaconStateError(BeaconStateError),
}
/// Returns the genesis `BeaconState`
///
/// Spec v0.5.0
pub fn get_genesis_state(
genesis_validator_deposits: &[Deposit],
genesis_time: u64,
genesis_eth1_data: Eth1Data,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// Get the genesis `BeaconState`
let mut state = BeaconState::genesis(genesis_time, genesis_eth1_data, spec);
// Process genesis deposits.
process_deposits(&mut state, genesis_validator_deposits, spec)?;
// Process genesis activations.
for i in 0..state.validator_registry.len() {
if state.get_effective_balance(i, spec)? >= spec.max_deposit_amount {
state.validator_registry[i].activation_epoch = spec.genesis_epoch;
}
}
// Ensure the current epoch cache is built.
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
// Set all the active index roots to be the genesis active index root.
let active_validator_indices = state
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
.to_vec();
let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.hash_tree_root());
state.fill_active_index_roots_with(genesis_active_index_root, spec);
// Generate the current shuffling seed.
state.current_shuffling_seed = state.generate_seed(spec.genesis_epoch, spec)?;
Ok(())
}
impl From<BlockProcessingError> for GenesisError {
fn from(e: BlockProcessingError) -> GenesisError {
GenesisError::BlockProcessingError(e)
}
}
impl From<BeaconStateError> for GenesisError {
fn from(e: BeaconStateError) -> GenesisError {
GenesisError::BeaconStateError(e)
}
}

View File

@ -1,10 +1,13 @@
#[macro_use]
mod macros;
pub mod common;
pub mod get_genesis_state;
pub mod per_block_processing;
pub mod per_epoch_processing;
pub mod per_slot_processing;
pub use get_genesis_state::get_genesis_state;
pub use per_block_processing::{
errors::{BlockInvalid, BlockProcessingError},
per_block_processing, per_block_processing_without_verifying_block_signature,

View File

@ -1,17 +1,15 @@
use self::verify_proposer_slashing::verify_proposer_slashing;
use crate::common::slash_validator;
use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
use hashing::hash;
use rayon::prelude::*;
use ssz::{ssz_encode, SignedRoot, TreeHash};
use ssz::{SignedRoot, TreeHash};
use types::*;
pub use self::verify_attester_slashing::{
gather_attester_slashing_indices, verify_attester_slashing,
};
pub use validate_attestation::{validate_attestation, validate_attestation_without_signature};
pub use verify_deposit::{
build_public_key_hashmap, get_existing_validator_index, verify_deposit, verify_deposit_index,
};
pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
pub use verify_exit::verify_exit;
pub use verify_slashable_attestation::verify_slashable_attestation;
pub use verify_transfer::{execute_transfer, verify_transfer};
@ -35,7 +33,7 @@ const VERIFY_DEPOSIT_MERKLE_PROOFS: bool = false;
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
/// returns an error describing why the block was invalid or how the function failed to execute.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn per_block_processing(
state: &mut BeaconState,
block: &BeaconBlock,
@ -50,7 +48,7 @@ pub fn per_block_processing(
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
/// returns an error describing why the block was invalid or how the function failed to execute.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn per_block_processing_without_verifying_block_signature(
state: &mut BeaconState,
block: &BeaconBlock,
@ -65,25 +63,24 @@ pub fn per_block_processing_without_verifying_block_signature(
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
/// returns an error describing why the block was invalid or how the function failed to execute.
///
/// Spec v0.4.0
/// Spec v0.5.0
fn per_block_processing_signature_optional(
mut state: &mut BeaconState,
block: &BeaconBlock,
should_verify_block_signature: bool,
spec: &ChainSpec,
) -> Result<(), Error> {
// Verify that `block.slot == state.slot`.
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
process_block_header(state, block, spec)?;
// Ensure the current and previous epoch cache is built.
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
if should_verify_block_signature {
verify_block_signature(&state, &block, &spec)?;
}
process_randao(&mut state, &block, &spec)?;
process_eth1_data(&mut state, &block.eth1_data)?;
process_eth1_data(&mut state, &block.body.eth1_data)?;
process_proposer_slashings(&mut state, &block.body.proposer_slashings, spec)?;
process_attester_slashings(&mut state, &block.body.attester_slashings, spec)?;
process_attestations(&mut state, &block.body.attestations, spec)?;
@ -94,33 +91,50 @@ fn per_block_processing_signature_optional(
Ok(())
}
/// Processes the block header.
///
/// Spec v0.5.0
pub fn process_block_header(
state: &mut BeaconState,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error> {
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
// NOTE: this is not to spec. I think spec is broken. See:
//
// https://github.com/ethereum/eth2.0-specs/issues/797
verify!(
block.previous_block_root == *state.get_block_root(state.slot - 1, spec)?,
Invalid::ParentBlockRootMismatch
);
state.latest_block_header = block.temporary_block_header(spec);
Ok(())
}
/// Verifies the signature of a block.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_block_signature(
state: &BeaconState,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error> {
let block_proposer =
&state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?];
let block_proposer = &state.validator_registry
[state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?];
let proposal = Proposal {
slot: block.slot,
shard: spec.beacon_chain_shard_number,
block_root: Hash256::from_slice(&block.signed_root()[..]),
signature: block.signature.clone(),
};
let domain = spec.get_domain(
block.slot.epoch(spec.slots_per_epoch),
Domain::Proposal,
Domain::BeaconBlock,
&state.fork,
);
verify!(
proposal
block
.signature
.verify(&proposal.signed_root()[..], domain, &block_proposer.pubkey),
.verify(&block.signed_root()[..], domain, &block_proposer.pubkey),
Invalid::BadSignature
);
@ -130,21 +144,18 @@ pub fn verify_block_signature(
/// Verifies the `randao_reveal` against the block's proposer pubkey and updates
/// `state.latest_randao_mixes`.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_randao(
state: &mut BeaconState,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error> {
// Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`.
let block_proposer =
&state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?];
let block_proposer = &state.validator_registry
[state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?];
// Verify that `bls_verify(pubkey=proposer.pubkey,
// message_hash=hash_tree_root(get_current_epoch(state)), signature=block.randao_reveal,
// domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO))`.
// Verify the RANDAO is a valid signature of the proposer.
verify!(
block.randao_reveal.verify(
block.body.randao_reveal.verify(
&state.current_epoch(spec).hash_tree_root()[..],
spec.get_domain(
block.slot.epoch(spec.slots_per_epoch),
@ -156,21 +167,23 @@ pub fn process_randao(
Invalid::BadRandaoSignature
);
// Update the state's RANDAO mix with the one revealed in the block.
update_randao(state, &block.randao_reveal, spec)?;
// Update the current epoch RANDAO mix.
state.update_randao_mix(state.current_epoch(spec), &block.body.randao_reveal, spec)?;
Ok(())
}
/// Update the `state.eth1_data_votes` based upon the `eth1_data` provided.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Result<(), Error> {
// Either increment the eth1_data vote count, or add a new eth1_data.
// Attempt to find a `Eth1DataVote` with matching `Eth1Data`.
let matching_eth1_vote_index = state
.eth1_data_votes
.iter()
.position(|vote| vote.eth1_data == *eth1_data);
// If a vote exists, increment it's `vote_count`. Otherwise, create a new `Eth1DataVote`.
if let Some(index) = matching_eth1_vote_index {
state.eth1_data_votes[index].vote_count += 1;
} else {
@ -183,46 +196,12 @@ pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Resul
Ok(())
}
/// Updates the present randao mix.
///
/// Set `state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] =
/// xor(get_randao_mix(state, get_current_epoch(state)), hash(block.randao_reveal))`.
///
/// Spec v0.4.0
pub fn update_randao(
state: &mut BeaconState,
reveal: &Signature,
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
let hashed_reveal = {
let encoded_signature = ssz_encode(reveal);
Hash256::from_slice(&hash(&encoded_signature[..])[..])
};
let current_epoch = state.slot.epoch(spec.slots_per_epoch);
let current_mix = state
.get_randao_mix(current_epoch, spec)
.ok_or_else(|| BeaconStateError::InsufficientRandaoMixes)?;
let new_mix = *current_mix ^ hashed_reveal;
let index = current_epoch.as_usize() % spec.latest_randao_mixes_length;
if index < state.latest_randao_mixes.len() {
state.latest_randao_mixes[index] = new_mix;
Ok(())
} else {
Err(BeaconStateError::InsufficientRandaoMixes)
}
}
/// Validates each `ProposerSlashing` and updates the state, short-circuiting on an invalid object.
///
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_proposer_slashings(
state: &mut BeaconState,
proposer_slashings: &[ProposerSlashing],
@ -242,8 +221,9 @@ pub fn process_proposer_slashings(
.map_err(|e| e.into_with_index(i))
})?;
// Update the state.
for proposer_slashing in proposer_slashings {
state.slash_validator(proposer_slashing.proposer_index as usize, spec)?;
slash_validator(state, proposer_slashing.proposer_index as usize, spec)?;
}
Ok(())
@ -254,7 +234,7 @@ pub fn process_proposer_slashings(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_attester_slashings(
state: &mut BeaconState,
attester_slashings: &[AttesterSlashing],
@ -296,11 +276,11 @@ pub fn process_attester_slashings(
)
.map_err(|e| e.into_with_index(i))?;
let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing)
let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing, spec)
.map_err(|e| e.into_with_index(i))?;
for i in slashable_indices {
state.slash_validator(i as usize, spec)?;
slash_validator(state, i as usize, spec)?;
}
}
@ -312,7 +292,7 @@ pub fn process_attester_slashings(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_attestations(
state: &mut BeaconState,
attestations: &[Attestation],
@ -342,7 +322,14 @@ pub fn process_attestations(
custody_bitfield: attestation.custody_bitfield.clone(),
inclusion_slot: state.slot,
};
state.latest_attestations.push(pending_attestation);
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
if attestation_epoch == state.current_epoch(spec) {
state.current_epoch_attestations.push(pending_attestation)
} else if attestation_epoch == state.previous_epoch(spec) {
state.previous_epoch_attestations.push(pending_attestation)
}
}
Ok(())
@ -353,7 +340,7 @@ pub fn process_attestations(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_deposits(
state: &mut BeaconState,
deposits: &[Deposit],
@ -373,19 +360,20 @@ pub fn process_deposits(
.map_err(|e| e.into_with_index(i))
})?;
let public_key_to_index_hashmap = build_public_key_hashmap(&state);
// Check `state.deposit_index` and update the state in series.
for (i, deposit) in deposits.iter().enumerate() {
verify_deposit_index(state, deposit).map_err(|e| e.into_with_index(i))?;
// Ensure the state's pubkey cache is fully up-to-date, it will be used to check to see if the
// depositing validator already exists in the registry.
state.update_pubkey_cache()?;
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
// already exists in the beacon_state.
//
// This function also verifies the withdrawal credentials.
let validator_index =
get_existing_validator_index(state, deposit, &public_key_to_index_hashmap)
.map_err(|e| e.into_with_index(i))?;
get_existing_validator_index(state, deposit).map_err(|e| e.into_with_index(i))?;
let deposit_data = &deposit.deposit_data;
let deposit_input = &deposit.deposit_data.deposit_input;
@ -400,7 +388,7 @@ pub fn process_deposits(
// Create a new validator.
let validator = Validator {
pubkey: deposit_input.pubkey.clone(),
withdrawal_credentials: deposit_input.withdrawal_credentials.clone(),
withdrawal_credentials: deposit_input.withdrawal_credentials,
activation_epoch: spec.far_future_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
@ -422,7 +410,7 @@ pub fn process_deposits(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_exits(
state: &mut BeaconState,
voluntary_exits: &[VoluntaryExit],
@ -454,7 +442,7 @@ pub fn process_exits(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_transfers(
state: &mut BeaconState,
transfers: &[Transfer],

View File

@ -67,6 +67,7 @@ impl_from_beacon_state_error!(BlockProcessingError);
#[derive(Debug, PartialEq)]
pub enum BlockInvalid {
StateSlotMismatch,
ParentBlockRootMismatch,
BadSignature,
BadRandaoSignature,
MaxAttestationsExceeded,
@ -112,45 +113,53 @@ pub enum AttestationValidationError {
#[derive(Debug, PartialEq)]
pub enum AttestationInvalid {
/// Attestation references a pre-genesis slot.
///
/// (genesis_slot, attestation_slot)
PreGenesis(Slot, Slot),
PreGenesis { genesis: Slot, attestation: Slot },
/// Attestation included before the inclusion delay.
///
/// (state_slot, inclusion_delay, attestation_slot)
IncludedTooEarly(Slot, u64, Slot),
IncludedTooEarly {
state: Slot,
delay: u64,
attestation: Slot,
},
/// Attestation slot is too far in the past to be included in a block.
///
/// (state_slot, attestation_slot)
IncludedTooLate(Slot, Slot),
IncludedTooLate { state: Slot, attestation: Slot },
/// Attestation justified epoch does not match the states current or previous justified epoch.
///
/// (attestation_justified_epoch, state_epoch, used_previous_epoch)
WrongJustifiedEpoch(Epoch, Epoch, bool),
/// `is_current` is `true` if the attestation was compared to the
/// `state.current_justified_epoch`, `false` if compared to `state.previous_justified_epoch`.
WrongJustifiedEpoch {
state: Epoch,
attestation: Epoch,
is_current: bool,
},
/// Attestation justified epoch root does not match root known to the state.
///
/// (state_justified_root, attestation_justified_root)
WrongJustifiedRoot(Hash256, Hash256),
/// `is_current` is `true` if the attestation was compared to the
/// `state.current_justified_epoch`, `false` if compared to `state.previous_justified_epoch`.
WrongJustifiedRoot {
state: Hash256,
attestation: Hash256,
is_current: bool,
},
/// Attestation crosslink root does not match the state crosslink root for the attestations
/// slot.
BadLatestCrosslinkRoot,
BadPreviousCrosslink,
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
CustodyBitfieldHasSetBits,
/// There are no set bits on the attestation -- an attestation must be signed by at least one
/// validator.
AggregationBitfieldIsEmpty,
/// The custody bitfield length is not the smallest possible size to represent the committee.
///
/// (committee_len, bitfield_len)
BadCustodyBitfieldLength(usize, usize),
BadCustodyBitfieldLength {
committee_len: usize,
bitfield_len: usize,
},
/// The aggregation bitfield length is not the smallest possible size to represent the committee.
///
/// (committee_len, bitfield_len)
BadAggregationBitfieldLength(usize, usize),
/// There was no known committee for the given shard in the given slot.
///
/// (attestation_data_shard, attestation_data_slot)
NoCommitteeForShard(u64, Slot),
BadAggregationBitfieldLength {
committee_len: usize,
bitfield_len: usize,
},
/// There was no known committee in this `epoch` for the given shard and slot.
NoCommitteeForShard { shard: u64, slot: Slot },
/// The validator index was unknown.
UnknownValidator(u64),
/// The attestation signature verification failed.
@ -188,6 +197,8 @@ pub enum AttesterSlashingInvalid {
SlashableAttestation2Invalid(SlashableAttestationInvalid),
/// The validator index is unknown. One cannot slash one who does not exist.
UnknownValidator(u64),
/// The specified validator has already been withdrawn.
ValidatorAlreadyWithdrawn(u64),
/// There were no indices able to be slashed.
NoSlashableIndices,
}
@ -264,16 +275,12 @@ pub enum ProposerSlashingInvalid {
///
/// (proposal_1_slot, proposal_2_slot)
ProposalSlotMismatch(Slot, Slot),
/// The two proposal have different shards.
///
/// (proposal_1_shard, proposal_2_shard)
ProposalShardMismatch(u64, u64),
/// The two proposal have different block roots.
///
/// (proposal_1_root, proposal_2_root)
ProposalBlockRootMismatch(Hash256, Hash256),
/// The proposals are identical and therefore not slashable.
ProposalsIdentical,
/// The specified proposer has already been slashed.
ProposerAlreadySlashed,
/// The specified proposer has already been withdrawn.
ProposerAlreadyWithdrawn(u64),
/// The first proposal signature was invalid.
BadProposal1Signature,
/// The second proposal signature was invalid.
@ -294,15 +301,15 @@ impl_into_with_index_without_beacon_error!(
pub enum DepositValidationError {
/// Validation completed successfully and the object is invalid.
Invalid(DepositInvalid),
/// Encountered a `BeaconStateError` whilst attempting to determine validity.
BeaconStateError(BeaconStateError),
}
/// Describes why an object is invalid.
#[derive(Debug, PartialEq)]
pub enum DepositInvalid {
/// The deposit index does not match the state index.
///
/// (state_index, deposit_index)
BadIndex(u64, u64),
BadIndex { state: u64, deposit: u64 },
/// The proof-of-possession does not match the given pubkey.
BadProofOfPossession,
/// The withdrawal credentials for the depositing validator did not match the withdrawal
@ -313,7 +320,8 @@ pub enum DepositInvalid {
BadMerkleProof,
}
impl_into_with_index_without_beacon_error!(DepositValidationError, DepositInvalid);
impl_from_beacon_state_error!(DepositValidationError);
impl_into_with_index_with_beacon_error!(DepositValidationError, DepositInvalid);
/*
* `Exit` Validation
@ -331,11 +339,14 @@ pub enum ExitValidationError {
pub enum ExitInvalid {
/// The specified validator is not in the state's validator registry.
ValidatorUnknown(u64),
AlreadyExited,
/// The specified validator has a non-maximum exit epoch.
AlreadyExited(u64),
/// The specified validator has already initiated exit.
AlreadyInitiatedExited(u64),
/// The exit is for a future epoch.
///
/// (state_epoch, exit_epoch)
FutureEpoch(Epoch, Epoch),
FutureEpoch { state: Epoch, exit: Epoch },
/// The validator has not been active for long enough.
TooYoungToLeave { lifespan: Epoch, expected: u64 },
/// The exit signature was not signed by the validator.
BadSignature,
}

View File

@ -1,6 +1,6 @@
use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
use crate::common::verify_bitfield_length;
use ssz::TreeHash;
use types::beacon_state::helpers::*;
use types::*;
/// Indicates if an `Attestation` is valid to be included in a block in the current epoch of the
@ -8,7 +8,7 @@ use types::*;
///
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn validate_attestation(
state: &BeaconState,
attestation: &Attestation,
@ -22,7 +22,7 @@ pub fn validate_attestation(
///
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn validate_attestation_without_signature(
state: &BeaconState,
attestation: &Attestation,
@ -35,74 +35,83 @@ pub fn validate_attestation_without_signature(
/// given state, optionally validating the aggregate signature.
///
///
/// Spec v0.4.0
/// Spec v0.5.0
fn validate_attestation_signature_optional(
state: &BeaconState,
attestation: &Attestation,
spec: &ChainSpec,
verify_signature: bool,
) -> Result<(), Error> {
// Verify that `attestation.data.slot >= GENESIS_SLOT`.
let state_epoch = state.slot.epoch(spec.slots_per_epoch);
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
// Can't submit pre-historic attestations.
verify!(
attestation.data.slot >= spec.genesis_slot,
Invalid::PreGenesis(spec.genesis_slot, attestation.data.slot)
Invalid::PreGenesis {
genesis: spec.genesis_slot,
attestation: attestation.data.slot
}
);
// Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`.
// Can't submit attestations too far in history.
verify!(
state.slot <= attestation.data.slot + spec.slots_per_epoch,
Invalid::IncludedTooLate {
state: spec.genesis_slot,
attestation: attestation.data.slot
}
);
// Can't submit attestation too quickly.
verify!(
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
Invalid::IncludedTooEarly(
state.slot,
spec.min_attestation_inclusion_delay,
attestation.data.slot
)
Invalid::IncludedTooEarly {
state: state.slot,
delay: spec.min_attestation_inclusion_delay,
attestation: attestation.data.slot
}
);
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`.
verify!(
state.slot < attestation.data.slot + spec.slots_per_epoch,
Invalid::IncludedTooLate(state.slot, attestation.data.slot)
);
// Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch` if
// `slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state) else
// state.previous_justified_epoch`.
if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) {
// Verify the justified epoch and root is correct.
if attestation_epoch >= state_epoch {
verify!(
attestation.data.justified_epoch == state.justified_epoch,
Invalid::WrongJustifiedEpoch(
attestation.data.justified_epoch,
state.justified_epoch,
false
)
attestation.data.source_epoch == state.current_justified_epoch,
Invalid::WrongJustifiedEpoch {
state: state.current_justified_epoch,
attestation: attestation.data.source_epoch,
is_current: true,
}
);
verify!(
attestation.data.source_root == state.current_justified_root,
Invalid::WrongJustifiedRoot {
state: state.current_justified_root,
attestation: attestation.data.source_root,
is_current: true,
}
);
} else {
verify!(
attestation.data.justified_epoch == state.previous_justified_epoch,
Invalid::WrongJustifiedEpoch(
attestation.data.justified_epoch,
state.previous_justified_epoch,
true
)
attestation.data.source_epoch == state.previous_justified_epoch,
Invalid::WrongJustifiedEpoch {
state: state.previous_justified_epoch,
attestation: attestation.data.source_epoch,
is_current: false,
}
);
verify!(
attestation.data.source_root == state.previous_justified_root,
Invalid::WrongJustifiedRoot {
state: state.previous_justified_root,
attestation: attestation.data.source_root,
is_current: true,
}
);
}
// Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state,
// get_epoch_start_slot(attestation.data.justified_epoch))`.
let justified_block_root = *state
.get_block_root(
attestation
.data
.justified_epoch
.start_slot(spec.slots_per_epoch),
&spec,
)
.ok_or(BeaconStateError::InsufficientBlockRoots)?;
verify!(
attestation.data.justified_block_root == justified_block_root,
Invalid::WrongJustifiedRoot(justified_block_root, attestation.data.justified_block_root)
);
// Check that the crosslink data is valid.
//
// Verify that either:
//
// (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`,
@ -115,63 +124,62 @@ fn validate_attestation_signature_optional(
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
};
verify!(
(attestation.data.latest_crosslink
(attestation.data.previous_crosslink
== state.latest_crosslinks[attestation.data.shard as usize])
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
Invalid::BadLatestCrosslinkRoot
Invalid::BadPreviousCrosslink
);
// Get the committee for this attestation
let (committee, _shard) = state
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
.iter()
.find(|(_committee, shard)| *shard == attestation.data.shard)
.ok_or_else(|| {
Error::Invalid(Invalid::NoCommitteeForShard(
attestation.data.shard,
attestation.data.slot,
))
})?;
// Custody bitfield is all zeros (phase 0 requirement).
verify!(
attestation.custody_bitfield.num_set_bits() == 0,
Invalid::CustodyBitfieldHasSetBits
);
// Custody bitfield length is correct.
verify!(
verify_bitfield_length(&attestation.custody_bitfield, committee.len()),
Invalid::BadCustodyBitfieldLength(committee.len(), attestation.custody_bitfield.len())
);
// Aggregation bitfield isn't empty.
// Attestation must be non-empty!
verify!(
attestation.aggregation_bitfield.num_set_bits() != 0,
Invalid::AggregationBitfieldIsEmpty
);
// Custody bitfield must be empty (be be removed in phase 1)
verify!(
attestation.custody_bitfield.num_set_bits() == 0,
Invalid::CustodyBitfieldHasSetBits
);
// Get the committee for the specific shard that this attestation is for.
let crosslink_committee = state
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
.iter()
.find(|c| c.shard == attestation.data.shard)
.ok_or_else(|| {
Error::Invalid(Invalid::NoCommitteeForShard {
shard: attestation.data.shard,
slot: attestation.data.slot,
})
})?;
let committee = &crosslink_committee.committee;
// Custody bitfield length is correct.
//
// This is not directly in the spec, but it is inferred.
verify!(
verify_bitfield_length(&attestation.custody_bitfield, committee.len()),
Invalid::BadCustodyBitfieldLength {
committee_len: committee.len(),
bitfield_len: attestation.custody_bitfield.len()
}
);
// Aggregation bitfield length is correct.
//
// This is not directly in the spec, but it is inferred.
verify!(
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
Invalid::BadAggregationBitfieldLength(
committee.len(),
attestation.aggregation_bitfield.len()
)
Invalid::BadAggregationBitfieldLength {
committee_len: committee.len(),
bitfield_len: attestation.custody_bitfield.len()
}
);
if verify_signature {
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
verify_attestation_signature(
state,
committee,
attestation_epoch,
&attestation.aggregation_bitfield,
&attestation.custody_bitfield,
&attestation.data,
&attestation.aggregate_signature,
spec,
)?;
verify_attestation_signature(state, committee, attestation, spec)?;
}
// [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`.
// Crosslink data root is zero (to be removed in phase 1).
verify!(
attestation.data.crosslink_data_root == spec.zero_hash,
Invalid::ShardBlockRootNotZero
@ -188,37 +196,34 @@ fn validate_attestation_signature_optional(
/// - `custody_bitfield` does not have a bit for each index of `committee`.
/// - A `validator_index` in `committee` is not in `state.validator_registry`.
///
/// Spec v0.4.0
/// Spec v0.5.0
fn verify_attestation_signature(
state: &BeaconState,
committee: &[usize],
attestation_epoch: Epoch,
aggregation_bitfield: &Bitfield,
custody_bitfield: &Bitfield,
attestation_data: &AttestationData,
aggregate_signature: &AggregateSignature,
a: &Attestation,
spec: &ChainSpec,
) -> Result<(), Error> {
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
let mut message_exists = vec![false; 2];
let attestation_epoch = a.data.slot.epoch(spec.slots_per_epoch);
for (i, v) in committee.iter().enumerate() {
let validator_signed = aggregation_bitfield.get(i).map_err(|_| {
Error::Invalid(Invalid::BadAggregationBitfieldLength(
committee.len(),
aggregation_bitfield.len(),
))
let validator_signed = a.aggregation_bitfield.get(i).map_err(|_| {
Error::Invalid(Invalid::BadAggregationBitfieldLength {
committee_len: committee.len(),
bitfield_len: a.aggregation_bitfield.len(),
})
})?;
if validator_signed {
let custody_bit: bool = match custody_bitfield.get(i) {
let custody_bit: bool = match a.custody_bitfield.get(i) {
Ok(bit) => bit,
// Invalidate signature if custody_bitfield.len() < committee
Err(_) => {
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength(
committee.len(),
custody_bitfield.len(),
)));
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength {
committee_len: committee.len(),
bitfield_len: a.aggregation_bitfield.len(),
}));
}
};
@ -236,14 +241,14 @@ fn verify_attestation_signature(
// Message when custody bitfield is `false`
let message_0 = AttestationDataAndCustodyBit {
data: attestation_data.clone(),
data: a.data.clone(),
custody_bit: false,
}
.hash_tree_root();
// Message when custody bitfield is `true`
let message_1 = AttestationDataAndCustodyBit {
data: attestation_data.clone(),
data: a.data.clone(),
custody_bit: true,
}
.hash_tree_root();
@ -265,7 +270,8 @@ fn verify_attestation_signature(
let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork);
verify!(
aggregate_signature.verify_multiple(&messages[..], domain, &keys[..]),
a.aggregate_signature
.verify_multiple(&messages[..], domain, &keys[..]),
Invalid::BadSignature
);

View File

@ -7,7 +7,7 @@ use types::*;
///
/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_attester_slashing(
state: &BeaconState,
attester_slashing: &AttesterSlashing,
@ -41,15 +41,16 @@ pub fn verify_attester_slashing(
///
/// Returns Ok(indices) if `indices.len() > 0`.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn gather_attester_slashing_indices(
state: &BeaconState,
attester_slashing: &AttesterSlashing,
spec: &ChainSpec,
) -> Result<Vec<u64>, Error> {
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
let mut slashable_indices = vec![];
let mut slashable_indices = Vec::with_capacity(spec.max_indices_per_slashable_vote);
for i in &slashable_attestation_1.validator_indices {
let validator = state
.validator_registry
@ -57,11 +58,20 @@ pub fn gather_attester_slashing_indices(
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?;
if slashable_attestation_2.validator_indices.contains(&i) & !validator.slashed {
// TODO: verify that we should reject any slashable attestation which includes a
// withdrawn validator. PH has asked the question on gitter, awaiting response.
verify!(
validator.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
Invalid::ValidatorAlreadyWithdrawn(*i)
);
slashable_indices.push(*i);
}
}
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
slashable_indices.shrink_to_fit();
Ok(slashable_indices)
}

View File

@ -3,11 +3,8 @@ use hashing::hash;
use merkle_proof::verify_merkle_proof;
use ssz::ssz_encode;
use ssz_derive::Encode;
use std::collections::HashMap;
use types::*;
pub type PublicKeyValidatorIndexHashmap = HashMap<PublicKey, u64>;
/// Indicates if a `Deposit` is valid to be included in a block in the current epoch of the given
/// state.
///
@ -18,7 +15,7 @@ pub type PublicKeyValidatorIndexHashmap = HashMap<PublicKey, u64>;
///
/// Note: this function is incomplete.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_deposit(
state: &BeaconState,
deposit: &Deposit,
@ -49,34 +46,32 @@ pub fn verify_deposit(
/// Verify that the `Deposit` index is correct.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_deposit_index(state: &BeaconState, deposit: &Deposit) -> Result<(), Error> {
verify!(
deposit.index == state.deposit_index,
Invalid::BadIndex(state.deposit_index, deposit.index)
Invalid::BadIndex {
state: state.deposit_index,
deposit: deposit.index
}
);
Ok(())
}
pub fn build_public_key_hashmap(state: &BeaconState) -> PublicKeyValidatorIndexHashmap {
let mut hashmap = HashMap::with_capacity(state.validator_registry.len());
for (i, validator) in state.validator_registry.iter().enumerate() {
hashmap.insert(validator.pubkey.clone(), i as u64);
}
hashmap
}
/// Returns a `Some(validator index)` if a pubkey already exists in the `validator_registry`,
/// otherwise returns `None`.
///
/// ## Errors
///
/// Errors if the state's `pubkey_cache` is not current.
pub fn get_existing_validator_index(
state: &BeaconState,
deposit: &Deposit,
pubkey_map: &HashMap<PublicKey, u64>,
) -> Result<Option<u64>, Error> {
let deposit_input = &deposit.deposit_data.deposit_input;
let validator_index = pubkey_map.get(&deposit_input.pubkey).and_then(|i| Some(*i));
let validator_index = state.get_validator_index(&deposit_input.pubkey)?;
match validator_index {
None => Ok(None),
@ -86,19 +81,19 @@ pub fn get_existing_validator_index(
== state.validator_registry[index as usize].withdrawal_credentials,
Invalid::BadWithdrawalCredentials
);
Ok(Some(index))
Ok(Some(index as u64))
}
}
}
/// Verify that a deposit is included in the state's eth1 deposit root.
///
/// Spec v0.4.0
/// Spec v0.5.0
fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &ChainSpec) -> bool {
let leaf = hash(&get_serialized_deposit_data(deposit));
verify_merkle_proof(
Hash256::from_slice(&leaf),
&deposit.branch,
&deposit.proof,
spec.deposit_contract_tree_depth as usize,
deposit.index as usize,
state.latest_eth1_data.deposit_root,
@ -107,7 +102,7 @@ fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &Ch
/// Helper struct for easily getting the serialized data generated by the deposit contract.
///
/// Spec v0.4.0
/// Spec v0.5.0
#[derive(Encode)]
struct SerializedDepositData {
amount: u64,
@ -118,7 +113,7 @@ struct SerializedDepositData {
/// Return the serialized data generated by the deposit contract that is used to generate the
/// merkle proof.
///
/// Spec v0.4.0
/// Spec v0.5.0
fn get_serialized_deposit_data(deposit: &Deposit) -> Vec<u8> {
let serialized_deposit_data = SerializedDepositData {
amount: deposit.deposit_data.amount,

View File

@ -7,7 +7,7 @@ use types::*;
///
/// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_exit(
state: &BeaconState,
exit: &VoluntaryExit,
@ -18,15 +18,35 @@ pub fn verify_exit(
.get(exit.validator_index as usize)
.ok_or_else(|| Error::Invalid(Invalid::ValidatorUnknown(exit.validator_index)))?;
// Verify that the validator has not yet exited.
verify!(
validator.exit_epoch
> state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec),
Invalid::AlreadyExited
validator.exit_epoch == spec.far_future_epoch,
Invalid::AlreadyExited(exit.validator_index)
);
// Verify that the validator has not yet initiated.
verify!(
!validator.initiated_exit,
Invalid::AlreadyInitiatedExited(exit.validator_index)
);
// Exits must specify an epoch when they become valid; they are not valid before then.
verify!(
state.current_epoch(spec) >= exit.epoch,
Invalid::FutureEpoch(state.current_epoch(spec), exit.epoch)
Invalid::FutureEpoch {
state: state.current_epoch(spec),
exit: exit.epoch
}
);
// Must have been in the validator set long enough.
let lifespan = state.slot.epoch(spec.slots_per_epoch) - validator.activation_epoch;
verify!(
lifespan >= spec.persistent_committee_period,
Invalid::TooYoungToLeave {
lifespan,
expected: spec.persistent_committee_period,
}
);
let message = exit.signed_root();

View File

@ -7,7 +7,7 @@ use types::*;
///
/// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_proposer_slashing(
proposer_slashing: &ProposerSlashing,
state: &BeaconState,
@ -21,34 +21,28 @@ pub fn verify_proposer_slashing(
})?;
verify!(
proposer_slashing.proposal_1.slot == proposer_slashing.proposal_2.slot,
proposer_slashing.header_1.slot == proposer_slashing.header_2.slot,
Invalid::ProposalSlotMismatch(
proposer_slashing.proposal_1.slot,
proposer_slashing.proposal_2.slot
proposer_slashing.header_1.slot,
proposer_slashing.header_2.slot
)
);
verify!(
proposer_slashing.proposal_1.shard == proposer_slashing.proposal_2.shard,
Invalid::ProposalShardMismatch(
proposer_slashing.proposal_1.shard,
proposer_slashing.proposal_2.shard
)
);
verify!(
proposer_slashing.proposal_1.block_root != proposer_slashing.proposal_2.block_root,
Invalid::ProposalBlockRootMismatch(
proposer_slashing.proposal_1.block_root,
proposer_slashing.proposal_2.block_root
)
proposer_slashing.header_1 != proposer_slashing.header_2,
Invalid::ProposalsIdentical
);
verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed);
verify!(
verify_proposal_signature(
&proposer_slashing.proposal_1,
proposer.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
Invalid::ProposerAlreadyWithdrawn(proposer_slashing.proposer_index)
);
verify!(
verify_header_signature(
&proposer_slashing.header_1,
&proposer.pubkey,
&state.fork,
spec
@ -56,8 +50,8 @@ pub fn verify_proposer_slashing(
Invalid::BadProposal1Signature
);
verify!(
verify_proposal_signature(
&proposer_slashing.proposal_2,
verify_header_signature(
&proposer_slashing.header_2,
&proposer.pubkey,
&state.fork,
spec
@ -71,17 +65,19 @@ pub fn verify_proposer_slashing(
/// Verifies the signature of a proposal.
///
/// Returns `true` if the signature is valid.
fn verify_proposal_signature(
proposal: &Proposal,
///
/// Spec v0.5.0
fn verify_header_signature(
header: &BeaconBlockHeader,
pubkey: &PublicKey,
fork: &Fork,
spec: &ChainSpec,
) -> bool {
let message = proposal.signed_root();
let message = header.signed_root();
let domain = spec.get_domain(
proposal.slot.epoch(spec.slots_per_epoch),
Domain::Proposal,
header.slot.epoch(spec.slots_per_epoch),
Domain::BeaconBlock,
fork,
);
proposal.signature.verify(&message[..], domain, pubkey)
header.signature.verify(&message[..], domain, pubkey)
}

View File

@ -1,8 +1,8 @@
use super::errors::{
SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error,
};
use crate::common::verify_bitfield_length;
use ssz::TreeHash;
use types::beacon_state::helpers::verify_bitfield_length;
use types::*;
/// Indicates if a `SlashableAttestation` is valid to be included in a block in the current epoch of the given
@ -10,7 +10,7 @@ use types::*;
///
/// Returns `Ok(())` if the `SlashableAttestation` is valid, otherwise indicates the reason for invalidity.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_slashable_attestation(
state: &BeaconState,
slashable_attestation: &SlashableAttestation,

View File

@ -10,16 +10,16 @@ use types::*;
///
/// Note: this function is incomplete.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn verify_transfer(
state: &BeaconState,
transfer: &Transfer,
spec: &ChainSpec,
) -> Result<(), Error> {
let from_balance = *state
let sender_balance = *state
.validator_balances
.get(transfer.from as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?;
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let total_amount = transfer
.amount
@ -27,19 +27,22 @@ pub fn verify_transfer(
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
verify!(
from_balance >= transfer.amount,
Invalid::FromBalanceInsufficient(transfer.amount, from_balance)
sender_balance >= transfer.amount,
Invalid::FromBalanceInsufficient(transfer.amount, sender_balance)
);
verify!(
from_balance >= transfer.fee,
Invalid::FromBalanceInsufficient(transfer.fee, from_balance)
sender_balance >= transfer.fee,
Invalid::FromBalanceInsufficient(transfer.fee, sender_balance)
);
verify!(
(from_balance == total_amount)
|| (from_balance >= (total_amount + spec.min_deposit_amount)),
Invalid::InvalidResultingFromBalance(from_balance - total_amount, spec.min_deposit_amount)
(sender_balance == total_amount)
|| (sender_balance >= (total_amount + spec.min_deposit_amount)),
Invalid::InvalidResultingFromBalance(
sender_balance - total_amount,
spec.min_deposit_amount
)
);
verify!(
@ -47,25 +50,25 @@ pub fn verify_transfer(
Invalid::StateSlotMismatch(state.slot, transfer.slot)
);
let from_validator = state
let sender_validator = state
.validator_registry
.get(transfer.from as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?;
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let epoch = state.slot.epoch(spec.slots_per_epoch);
verify!(
from_validator.is_withdrawable_at(epoch)
|| from_validator.activation_epoch == spec.far_future_epoch,
Invalid::FromValidatorIneligableForTransfer(transfer.from)
sender_validator.is_withdrawable_at(epoch)
|| sender_validator.activation_epoch == spec.far_future_epoch,
Invalid::FromValidatorIneligableForTransfer(transfer.sender)
);
let transfer_withdrawal_credentials = Hash256::from_slice(
&get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..],
);
verify!(
from_validator.withdrawal_credentials == transfer_withdrawal_credentials,
sender_validator.withdrawal_credentials == transfer_withdrawal_credentials,
Invalid::WithdrawalCredentialsMismatch(
from_validator.withdrawal_credentials,
sender_validator.withdrawal_credentials,
transfer_withdrawal_credentials
)
);
@ -91,22 +94,23 @@ pub fn verify_transfer(
///
/// Does not check that the transfer is valid, however checks for overflow in all actions.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn execute_transfer(
state: &mut BeaconState,
transfer: &Transfer,
spec: &ChainSpec,
) -> Result<(), Error> {
let from_balance = *state
let sender_balance = *state
.validator_balances
.get(transfer.from as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?;
let to_balance = *state
.get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let recipient_balance = *state
.validator_balances
.get(transfer.to as usize)
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.to)))?;
.get(transfer.recipient as usize)
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.recipient)))?;
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)?;
let proposer_index =
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
let proposer_balance = state.validator_balances[proposer_index];
let total_amount = transfer
@ -114,14 +118,22 @@ pub fn execute_transfer(
.checked_add(transfer.fee)
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
state.validator_balances[transfer.from as usize] =
from_balance.checked_sub(total_amount).ok_or_else(|| {
Error::Invalid(Invalid::FromBalanceInsufficient(total_amount, from_balance))
state.validator_balances[transfer.sender as usize] =
sender_balance.checked_sub(total_amount).ok_or_else(|| {
Error::Invalid(Invalid::FromBalanceInsufficient(
total_amount,
sender_balance,
))
})?;
state.validator_balances[transfer.to as usize] = to_balance
state.validator_balances[transfer.recipient as usize] = recipient_balance
.checked_add(transfer.amount)
.ok_or_else(|| Error::Invalid(Invalid::ToBalanceOverflow(to_balance, transfer.amount)))?;
.ok_or_else(|| {
Error::Invalid(Invalid::ToBalanceOverflow(
recipient_balance,
transfer.amount,
))
})?;
state.validator_balances[proposer_index] =
proposer_balance.checked_add(transfer.fee).ok_or_else(|| {

View File

@ -1,19 +1,25 @@
use attester_sets::AttesterSets;
use apply_rewards::apply_rewards;
use errors::EpochProcessingError as Error;
use fnv::FnvHashMap;
use fnv::FnvHashSet;
use integer_sqrt::IntegerSquareRoot;
use rayon::prelude::*;
use process_ejections::process_ejections;
use process_exit_queue::process_exit_queue;
use process_slashings::process_slashings;
use ssz::TreeHash;
use std::collections::HashMap;
use std::iter::FromIterator;
use types::{validator_registry::get_active_validator_indices, *};
use types::*;
use update_registry_and_shuffling_data::update_registry_and_shuffling_data;
use validator_statuses::{TotalBalances, ValidatorStatuses};
use winning_root::{winning_root, WinningRoot};
pub mod attester_sets;
pub mod apply_rewards;
pub mod errors;
pub mod get_attestation_participants;
pub mod inclusion_distance;
pub mod process_ejections;
pub mod process_exit_queue;
pub mod process_slashings;
pub mod tests;
pub mod update_registry_and_shuffling_data;
pub mod validator_statuses;
pub mod winning_root;
/// Maps a shard to a winning root.
@ -26,60 +32,51 @@ pub type WinningRootHashSet = HashMap<u64, WinningRoot>;
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
/// returned, a state might be "half-processed" and therefore in an invalid state.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
let previous_epoch = state.previous_epoch(spec);
// Ensure all of the caches are built.
// Ensure the previous and next epoch caches are built.
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
state.build_epoch_cache(RelativeEpoch::Next, spec)?;
let attesters = calculate_attester_sets(&state, spec)?;
// Load the struct we use to assign validators into sets based on their participation.
//
// E.g., attestation in the previous epoch, attested to the head, etc.
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
validator_statuses.process_attestations(&state, spec)?;
let active_validator_indices = calculate_active_validator_indices(&state, spec);
// Justification.
update_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec);
let previous_total_balance = state.get_total_balance(
&get_active_validator_indices(&state.validator_registry, previous_epoch)[..],
spec,
);
process_eth1_data(state, spec);
process_justification(
state,
current_total_balance,
previous_total_balance,
attesters.previous_epoch_boundary.balance,
attesters.current_epoch_boundary.balance,
spec,
);
// Crosslinks
// Crosslinks.
let winning_root_for_shards = process_crosslinks(state, spec)?;
// Rewards and Penalities
process_rewards_and_penalities(
// Eth1 data.
maybe_reset_eth1_period(state, spec);
// Rewards and Penalities.
apply_rewards(
state,
&active_validator_indices,
&attesters,
previous_total_balance,
&mut validator_statuses,
&winning_root_for_shards,
spec,
)?;
// Ejections
state.process_ejections(spec);
// Ejections.
process_ejections(state, spec)?;
// Validator Registry
process_validator_registry(state, spec)?;
// Validator Registry.
update_registry_and_shuffling_data(
state,
validator_statuses.total_balances.current_epoch,
spec,
)?;
// Final updates
update_active_tree_index_roots(state, spec)?;
update_latest_slashed_balances(state, spec);
clean_attestations(state, spec);
// Slashings and exit queue.
process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;
process_exit_queue(state, spec);
// Final updates.
finish_epoch_update(state, spec)?;
// Rotate the epoch caches to suit the epoch transition.
state.advance_caches();
@ -87,39 +84,16 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result
Ok(())
}
/// Returns a list of active validator indices for the state's current epoch.
/// Maybe resets the eth1 period.
///
/// Spec v0.4.0
pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) -> Vec<usize> {
get_active_validator_indices(
&state.validator_registry,
state.slot.epoch(spec.slots_per_epoch),
)
}
/// Calculates various sets of attesters, including:
///
/// - current epoch attesters
/// - current epoch boundary attesters
/// - previous epoch attesters
/// - etc.
///
/// Spec v0.4.0
pub fn calculate_attester_sets(
state: &BeaconState,
spec: &ChainSpec,
) -> Result<AttesterSets, BeaconStateError> {
AttesterSets::new(&state, spec)
}
/// Spec v0.4.0
pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) {
/// Spec v0.5.0
pub fn maybe_reset_eth1_period(state: &mut BeaconState, spec: &ChainSpec) {
let next_epoch = state.next_epoch(spec);
let voting_period = spec.epochs_per_eth1_voting_period;
if next_epoch % voting_period == 0 {
for eth1_data_vote in &state.eth1_data_votes {
if eth1_data_vote.vote_count * 2 > voting_period {
if eth1_data_vote.vote_count * 2 > voting_period * spec.slots_per_epoch {
state.latest_eth1_data = eth1_data_vote.eth1_data.clone();
}
}
@ -134,81 +108,68 @@ pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) {
/// - `justified_epoch`
/// - `previous_justified_epoch`
///
/// Spec v0.4.0
pub fn process_justification(
/// Spec v0.5.0
pub fn update_justification_and_finalization(
state: &mut BeaconState,
current_total_balance: u64,
previous_total_balance: u64,
previous_epoch_boundary_attesting_balance: u64,
current_epoch_boundary_attesting_balance: u64,
total_balances: &TotalBalances,
spec: &ChainSpec,
) {
) -> Result<(), Error> {
let previous_epoch = state.previous_epoch(spec);
let current_epoch = state.current_epoch(spec);
let mut new_justified_epoch = state.justified_epoch;
let mut new_justified_epoch = state.current_justified_epoch;
let mut new_finalized_epoch = state.finalized_epoch;
// Rotate the justification bitfield up one epoch to make room for the current epoch.
state.justification_bitfield <<= 1;
// If > 2/3 of the total balance attested to the previous epoch boundary
//
// - Set the 2nd bit of the bitfield.
// - Set the previous epoch to be justified.
if (3 * previous_epoch_boundary_attesting_balance) >= (2 * previous_total_balance) {
state.justification_bitfield |= 2;
// If the previous epoch gets justified, full the second last bit.
if (total_balances.previous_epoch_boundary_attesters * 3) >= (total_balances.previous_epoch * 2)
{
new_justified_epoch = previous_epoch;
state.justification_bitfield |= 2;
}
// If > 2/3 of the total balance attested to the previous epoch boundary
//
// - Set the 1st bit of the bitfield.
// - Set the current epoch to be justified.
if (3 * current_epoch_boundary_attesting_balance) >= (2 * current_total_balance) {
state.justification_bitfield |= 1;
// If the current epoch gets justified, fill the last bit.
if (total_balances.current_epoch_boundary_attesters * 3) >= (total_balances.current_epoch * 2) {
new_justified_epoch = current_epoch;
state.justification_bitfield |= 1;
}
// If:
//
// - All three epochs prior to this epoch have been justified.
// - The previous justified justified epoch was three epochs ago.
//
// Then, set the finalized epoch to be three epochs ago.
if ((state.justification_bitfield >> 1) % 8 == 0b111)
& (state.previous_justified_epoch == previous_epoch - 2)
{
state.finalized_epoch = state.previous_justified_epoch;
let bitfield = state.justification_bitfield;
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
if ((bitfield >> 1) % 8 == 0b111) & (state.previous_justified_epoch == current_epoch - 3) {
new_finalized_epoch = state.previous_justified_epoch;
}
// If:
//
// - Both two epochs prior to this epoch have been justified.
// - The previous justified epoch was two epochs ago.
//
// Then, set the finalized epoch to two epochs ago.
if ((state.justification_bitfield >> 1) % 4 == 0b11)
& (state.previous_justified_epoch == previous_epoch - 1)
{
state.finalized_epoch = state.previous_justified_epoch;
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
if ((bitfield >> 1) % 4 == 0b11) & (state.previous_justified_epoch == current_epoch - 2) {
new_finalized_epoch = state.previous_justified_epoch;
}
// If:
//
// - This epoch and the two prior have been justified.
// - The presently justified epoch was two epochs ago.
//
// Then, set the finalized epoch to two epochs ago.
if (state.justification_bitfield % 8 == 0b111) & (state.justified_epoch == previous_epoch - 1) {
state.finalized_epoch = state.justified_epoch;
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source.
if (bitfield % 8 == 0b111) & (state.current_justified_epoch == current_epoch - 2) {
new_finalized_epoch = state.current_justified_epoch;
}
// If:
//
// - This epoch and the epoch prior to it have been justified.
// - Set the previous epoch to be justified.
//
// Then, set the finalized epoch to be the previous epoch.
if (state.justification_bitfield % 4 == 0b11) & (state.justified_epoch == previous_epoch) {
state.finalized_epoch = state.justified_epoch;
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
if (bitfield % 4 == 0b11) & (state.current_justified_epoch == current_epoch - 1) {
new_finalized_epoch = state.current_justified_epoch;
}
state.previous_justified_epoch = state.justified_epoch;
state.justified_epoch = new_justified_epoch;
state.previous_justified_epoch = state.current_justified_epoch;
state.previous_justified_root = state.current_justified_root;
if new_justified_epoch != state.current_justified_epoch {
state.current_justified_epoch = new_justified_epoch;
state.current_justified_root =
*state.get_block_root(new_justified_epoch.start_slot(spec.slots_per_epoch), spec)?;
}
if new_finalized_epoch != state.finalized_epoch {
state.finalized_epoch = new_finalized_epoch;
state.finalized_root =
*state.get_block_root(new_finalized_epoch.start_slot(spec.slots_per_epoch), spec)?;
}
Ok(())
}
/// Updates the following fields on the `BeaconState`:
@ -217,23 +178,11 @@ pub fn process_justification(
///
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_crosslinks(
state: &mut BeaconState,
spec: &ChainSpec,
) -> Result<WinningRootHashSet, Error> {
let current_epoch_attestations: Vec<&PendingAttestation> = state
.latest_attestations
.par_iter()
.filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.current_epoch(spec))
.collect();
let previous_epoch_attestations: Vec<&PendingAttestation> = state
.latest_attestations
.par_iter()
.filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec))
.collect();
let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();
let previous_and_current_epoch_slots: Vec<Slot> = state
@ -247,24 +196,18 @@ pub fn process_crosslinks(
let crosslink_committees_at_slot =
state.get_crosslink_committees_at_slot(slot, spec)?.clone();
for (crosslink_committee, shard) in crosslink_committees_at_slot {
let shard = shard as u64;
for c in crosslink_committees_at_slot {
let shard = c.shard as u64;
let winning_root = winning_root(
state,
shard,
&current_epoch_attestations[..],
&previous_epoch_attestations[..],
spec,
)?;
let winning_root = winning_root(state, shard, spec)?;
if let Some(winning_root) = winning_root {
let total_committee_balance = state.get_total_balance(&crosslink_committee, spec);
let total_committee_balance = state.get_total_balance(&c.committee, spec)?;
// TODO: I think this has a bug.
if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) {
state.latest_crosslinks[shard as usize] = Crosslink {
epoch: state.current_epoch(spec),
epoch: slot.epoch(spec.slots_per_epoch),
crosslink_data_root: winning_root.crosslink_data_root,
}
}
@ -276,341 +219,53 @@ pub fn process_crosslinks(
Ok(winning_root_for_shards)
}
/// Updates the following fields on the BeaconState:
/// Finish up an epoch update.
///
/// - `validator_balances`
///
/// Spec v0.4.0
pub fn process_rewards_and_penalities(
state: &mut BeaconState,
active_validator_indices: &[usize],
attesters: &AttesterSets,
previous_total_balance: u64,
winning_root_for_shards: &WinningRootHashSet,
spec: &ChainSpec,
) -> Result<(), Error> {
/// Spec v0.5.0
pub fn finish_epoch_update(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec);
let next_epoch = state.next_epoch(spec);
let active_validator_indices: FnvHashSet<usize> =
FnvHashSet::from_iter(active_validator_indices.iter().cloned());
let previous_epoch_attestations: Vec<&PendingAttestation> = state
.latest_attestations
.par_iter()
.filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec))
.collect();
let base_reward_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient;
if base_reward_quotient == 0 {
return Err(Error::BaseRewardQuotientIsZero);
}
if previous_total_balance == 0 {
return Err(Error::PreviousTotalBalanceIsZero);
}
// Map is ValidatorIndex -> ProposerIndex
let mut inclusion_slots: FnvHashMap<usize, (Slot, usize)> = FnvHashMap::default();
for a in &previous_epoch_attestations {
let participants =
state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?;
let inclusion_distance = (a.inclusion_slot - a.data.slot).as_u64();
for participant in participants {
if let Some((existing_distance, _)) = inclusion_slots.get(&participant) {
if *existing_distance <= inclusion_distance {
continue;
}
}
let proposer_index = state
.get_beacon_proposer_index(a.data.slot, spec)
.map_err(|_| Error::UnableToDetermineProducer)?;
inclusion_slots.insert(
participant,
(Slot::from(inclusion_distance), proposer_index),
);
}
}
// Justification and finalization
let epochs_since_finality = next_epoch - state.finalized_epoch;
if epochs_since_finality <= 4 {
state.validator_balances = state
.validator_balances
.par_iter()
.enumerate()
.map(|(index, &balance)| {
let mut balance = balance;
let base_reward = state.base_reward(index, base_reward_quotient, spec);
// Expected FFG source
if attesters.previous_epoch.indices.contains(&index) {
safe_add_assign!(
balance,
base_reward * attesters.previous_epoch.balance / previous_total_balance
);
} else if active_validator_indices.contains(&index) {
safe_sub_assign!(balance, base_reward);
}
// Expected FFG target
if attesters.previous_epoch_boundary.indices.contains(&index) {
safe_add_assign!(
balance,
base_reward * attesters.previous_epoch_boundary.balance
/ previous_total_balance
);
} else if active_validator_indices.contains(&index) {
safe_sub_assign!(balance, base_reward);
}
// Expected beacon chain head
if attesters.previous_epoch_head.indices.contains(&index) {
safe_add_assign!(
balance,
base_reward * attesters.previous_epoch_head.balance
/ previous_total_balance
);
} else if active_validator_indices.contains(&index) {
safe_sub_assign!(balance, base_reward);
};
if attesters.previous_epoch.indices.contains(&index) {
let base_reward = state.base_reward(index, base_reward_quotient, spec);
let (inclusion_distance, _) = inclusion_slots
.get(&index)
.expect("Inconsistent inclusion_slots.");
if *inclusion_distance > 0 {
safe_add_assign!(
balance,
base_reward * spec.min_attestation_inclusion_delay
/ inclusion_distance.as_u64()
)
}
}
balance
})
.collect();
} else {
state.validator_balances = state
.validator_balances
.par_iter()
.enumerate()
.map(|(index, &balance)| {
let mut balance = balance;
let inactivity_penalty = state.inactivity_penalty(
index,
epochs_since_finality,
base_reward_quotient,
spec,
);
if active_validator_indices.contains(&index) {
if !attesters.previous_epoch.indices.contains(&index) {
safe_sub_assign!(balance, inactivity_penalty);
}
if !attesters.previous_epoch_boundary.indices.contains(&index) {
safe_sub_assign!(balance, inactivity_penalty);
}
if !attesters.previous_epoch_head.indices.contains(&index) {
safe_sub_assign!(balance, inactivity_penalty);
}
if state.validator_registry[index].slashed {
let base_reward = state.base_reward(index, base_reward_quotient, spec);
safe_sub_assign!(balance, 2 * inactivity_penalty + base_reward);
}
}
if attesters.previous_epoch.indices.contains(&index) {
let base_reward = state.base_reward(index, base_reward_quotient, spec);
let (inclusion_distance, _) = inclusion_slots
.get(&index)
.expect("Inconsistent inclusion_slots.");
if *inclusion_distance > 0 {
safe_add_assign!(
balance,
base_reward * spec.min_attestation_inclusion_delay
/ inclusion_distance.as_u64()
)
}
}
balance
})
.collect();
}
// Attestation inclusion
// This is a hack to allow us to update index roots and slashed balances for the next epoch.
//
for &index in &attesters.previous_epoch.indices {
let (_, proposer_index) = inclusion_slots
.get(&index)
.ok_or_else(|| Error::InclusionSlotsInconsistent(index))?;
let base_reward = state.base_reward(*proposer_index, base_reward_quotient, spec);
safe_add_assign!(
state.validator_balances[*proposer_index],
base_reward / spec.attestation_inclusion_reward_quotient
);
}
//Crosslinks
for slot in state.previous_epoch(spec).slot_iter(spec.slots_per_epoch) {
// Clone removes the borrow which becomes an issue when mutating `state.balances`.
let crosslink_committees_at_slot =
state.get_crosslink_committees_at_slot(slot, spec)?.clone();
for (crosslink_committee, shard) in crosslink_committees_at_slot {
let shard = shard as u64;
// Note: I'm a little uncertain of the logic here -- I am waiting for spec v0.5.0 to
// clear it up.
//
// What happens here is:
//
// - If there was some crosslink root elected by the super-majority of this committee,
// then we reward all who voted for that root and penalize all that did not.
// - However, if there _was not_ some super-majority-voted crosslink root, then penalize
// all the validators.
//
// I'm not quite sure that the second case (no super-majority crosslink) is correct.
if let Some(winning_root) = winning_root_for_shards.get(&shard) {
// Hash set de-dedups and (hopefully) offers a speed improvement from faster
// lookups.
let attesting_validator_indices: FnvHashSet<usize> =
FnvHashSet::from_iter(winning_root.attesting_validator_indices.iter().cloned());
for &index in &crosslink_committee {
let base_reward = state.base_reward(index, base_reward_quotient, spec);
let total_balance = state.get_total_balance(&crosslink_committee, spec);
if attesting_validator_indices.contains(&index) {
safe_add_assign!(
state.validator_balances[index],
base_reward * winning_root.total_attesting_balance / total_balance
);
} else {
safe_sub_assign!(state.validator_balances[index], base_reward);
}
}
} else {
for &index in &crosslink_committee {
let base_reward = state.base_reward(index, base_reward_quotient, spec);
safe_sub_assign!(state.validator_balances[index], base_reward);
}
}
}
}
Ok(())
}
/// Peforms a validator registry update, if required.
///
/// Spec v0.4.0
pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec);
let next_epoch = state.next_epoch(spec);
state.previous_shuffling_epoch = state.current_shuffling_epoch;
state.previous_shuffling_start_shard = state.current_shuffling_start_shard;
state.previous_shuffling_seed = state.current_shuffling_seed;
let should_update_validator_registy = if state.finalized_epoch
> state.validator_registry_update_epoch
// The indentation here is to make it obvious where the weird stuff happens.
{
(0..state.get_current_epoch_committee_count(spec)).all(|i| {
let shard = (state.current_shuffling_start_shard + i as u64) % spec.shard_count;
state.latest_crosslinks[shard as usize].epoch > state.validator_registry_update_epoch
})
} else {
false
};
state.slot += 1;
if should_update_validator_registy {
state.update_validator_registry(spec);
// Set active index root
let active_index_root = Hash256::from_slice(
&state
.get_active_validator_indices(next_epoch + spec.activation_exit_delay)
.hash_tree_root()[..],
);
state.set_active_index_root(next_epoch, active_index_root, spec)?;
state.current_shuffling_epoch = next_epoch;
state.current_shuffling_start_shard = (state.current_shuffling_start_shard
+ state.get_current_epoch_committee_count(spec) as u64)
% spec.shard_count;
state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)?
} else {
let epochs_since_last_registry_update =
current_epoch - state.validator_registry_update_epoch;
if (epochs_since_last_registry_update > 1)
& epochs_since_last_registry_update.is_power_of_two()
{
state.current_shuffling_epoch = next_epoch;
state.current_shuffling_seed =
state.generate_seed(state.current_shuffling_epoch, spec)?
}
// Set total slashed balances
state.set_slashed_balance(
next_epoch,
state.get_slashed_balance(current_epoch, spec)?,
spec,
)?;
// Set randao mix
state.set_randao_mix(
next_epoch,
*state.get_randao_mix(current_epoch, spec)?,
spec,
)?;
state.slot -= 1;
}
state.process_slashings(spec);
state.process_exit_queue(spec);
if next_epoch.as_u64() % (spec.slots_per_historical_root as u64 / spec.slots_per_epoch) == 0 {
let historical_batch: HistoricalBatch = state.historical_batch();
state
.historical_roots
.push(Hash256::from_slice(&historical_batch.hash_tree_root()[..]));
}
state.previous_epoch_attestations = state.current_epoch_attestations.clone();
state.current_epoch_attestations = vec![];
Ok(())
}
/// Updates the state's `latest_active_index_roots` field with a tree hash the active validator
/// indices for the next epoch.
///
/// Spec v0.4.0
pub fn update_active_tree_index_roots(
state: &mut BeaconState,
spec: &ChainSpec,
) -> Result<(), Error> {
let next_epoch = state.next_epoch(spec);
let active_tree_root = get_active_validator_indices(
&state.validator_registry,
next_epoch + Epoch::from(spec.activation_exit_delay),
)
.hash_tree_root();
state.latest_active_index_roots[(next_epoch.as_usize()
+ spec.activation_exit_delay as usize)
% spec.latest_active_index_roots_length] = Hash256::from_slice(&active_tree_root[..]);
Ok(())
}
/// Advances the state's `latest_slashed_balances` field.
///
/// Spec v0.4.0
pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) {
let current_epoch = state.current_epoch(spec);
let next_epoch = state.next_epoch(spec);
state.latest_slashed_balances[next_epoch.as_usize() % spec.latest_slashed_exit_length] =
state.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length];
}
/// Removes all pending attestations from the previous epoch.
///
/// Spec v0.4.0
pub fn clean_attestations(state: &mut BeaconState, spec: &ChainSpec) {
let current_epoch = state.current_epoch(spec);
state.latest_attestations = state
.latest_attestations
.iter()
.filter(|a| a.data.slot.epoch(spec.slots_per_epoch) >= current_epoch)
.cloned()
.collect();
}

View File

@ -0,0 +1,334 @@
use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
use super::{Error, WinningRootHashSet};
use integer_sqrt::IntegerSquareRoot;
use types::*;
/// Use to track the changes to a validators balance.
#[derive(Default, Clone)]
pub struct Delta {
rewards: u64,
penalties: u64,
}
impl Delta {
/// Reward the validator with the `reward`.
pub fn reward(&mut self, reward: u64) {
self.rewards += reward;
}
/// Penalize the validator with the `penalty`.
pub fn penalize(&mut self, penalty: u64) {
self.penalties += penalty;
}
}
impl std::ops::AddAssign for Delta {
/// Use wrapping addition as that is how it's defined in the spec.
fn add_assign(&mut self, other: Delta) {
self.rewards += other.rewards;
self.penalties += other.penalties;
}
}
/// Apply attester and proposer rewards.
///
/// Spec v0.5.0
pub fn apply_rewards(
state: &mut BeaconState,
validator_statuses: &mut ValidatorStatuses,
winning_root_for_shards: &WinningRootHashSet,
spec: &ChainSpec,
) -> Result<(), Error> {
// Guard against an out-of-bounds during the validator balance update.
if validator_statuses.statuses.len() != state.validator_balances.len() {
return Err(Error::ValidatorStatusesInconsistent);
}
// Guard against an out-of-bounds during the attester inclusion balance update.
if validator_statuses.statuses.len() != state.validator_registry.len() {
return Err(Error::ValidatorStatusesInconsistent);
}
let mut deltas = vec![Delta::default(); state.validator_balances.len()];
get_justification_and_finalization_deltas(&mut deltas, state, &validator_statuses, spec)?;
get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?;
// Apply the proposer deltas if we are finalizing normally.
//
// This is executed slightly differently to the spec because of the way our functions are
// structured. It should be functionally equivalent.
if epochs_since_finality(state, spec) <= 4 {
get_proposer_deltas(
&mut deltas,
state,
validator_statuses,
winning_root_for_shards,
spec,
)?;
}
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
for (i, delta) in deltas.iter().enumerate() {
state.validator_balances[i] += delta.rewards;
state.validator_balances[i] = state.validator_balances[i].saturating_sub(delta.penalties);
}
Ok(())
}
/// Applies the attestation inclusion reward to each proposer for every validator who included an
/// attestation in the previous epoch.
///
/// Spec v0.5.0
fn get_proposer_deltas(
deltas: &mut Vec<Delta>,
state: &mut BeaconState,
validator_statuses: &mut ValidatorStatuses,
winning_root_for_shards: &WinningRootHashSet,
spec: &ChainSpec,
) -> Result<(), Error> {
// Update statuses with the information from winning roots.
validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
let mut delta = Delta::default();
if validator.is_previous_epoch_attester {
let inclusion = validator
.inclusion_info
.expect("It is a logic error for an attester not to have an inclusion distance.");
let base_reward = get_base_reward(
state,
inclusion.proposer_index,
validator_statuses.total_balances.previous_epoch,
spec,
)?;
if inclusion.proposer_index >= deltas.len() {
return Err(Error::ValidatorStatusesInconsistent);
}
delta.reward(base_reward / spec.attestation_inclusion_reward_quotient);
}
deltas[index] += delta;
}
Ok(())
}
/// Apply rewards for participation in attestations during the previous epoch.
///
/// Spec v0.5.0
fn get_justification_and_finalization_deltas(
deltas: &mut Vec<Delta>,
state: &BeaconState,
validator_statuses: &ValidatorStatuses,
spec: &ChainSpec,
) -> Result<(), Error> {
let epochs_since_finality = epochs_since_finality(state, spec);
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
let base_reward = get_base_reward(
state,
index,
validator_statuses.total_balances.previous_epoch,
spec,
)?;
let inactivity_penalty = get_inactivity_penalty(
state,
index,
epochs_since_finality.as_u64(),
validator_statuses.total_balances.previous_epoch,
spec,
)?;
let delta = if epochs_since_finality <= 4 {
compute_normal_justification_and_finalization_delta(
&validator,
&validator_statuses.total_balances,
base_reward,
spec,
)
} else {
compute_inactivity_leak_delta(&validator, base_reward, inactivity_penalty, spec)
};
deltas[index] += delta;
}
Ok(())
}
/// Determine the delta for a single validator, if the chain is finalizing normally.
///
/// Spec v0.5.0
fn compute_normal_justification_and_finalization_delta(
validator: &ValidatorStatus,
total_balances: &TotalBalances,
base_reward: u64,
spec: &ChainSpec,
) -> Delta {
let mut delta = Delta::default();
let boundary_attesting_balance = total_balances.previous_epoch_boundary_attesters;
let total_balance = total_balances.previous_epoch;
let total_attesting_balance = total_balances.previous_epoch_attesters;
let matching_head_balance = total_balances.previous_epoch_boundary_attesters;
// Expected FFG source.
if validator.is_previous_epoch_attester {
delta.reward(base_reward * total_attesting_balance / total_balance);
// Inclusion speed bonus
let inclusion = validator
.inclusion_info
.expect("It is a logic error for an attester not to have an inclusion distance.");
delta.reward(
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
);
} else if validator.is_active_in_previous_epoch {
delta.penalize(base_reward);
}
// Expected FFG target.
if validator.is_previous_epoch_boundary_attester {
delta.reward(base_reward / boundary_attesting_balance / total_balance);
} else if validator.is_active_in_previous_epoch {
delta.penalize(base_reward);
}
// Expected head.
if validator.is_previous_epoch_head_attester {
delta.reward(base_reward * matching_head_balance / total_balance);
} else if validator.is_active_in_previous_epoch {
delta.penalize(base_reward);
};
// Proposer bonus is handled in `apply_proposer_deltas`.
//
// This function only computes the delta for a single validator, so it cannot also return a
// delta for a validator.
delta
}
/// Determine the delta for a single delta, assuming the chain is _not_ finalizing normally.
///
/// Spec v0.5.0
fn compute_inactivity_leak_delta(
validator: &ValidatorStatus,
base_reward: u64,
inactivity_penalty: u64,
spec: &ChainSpec,
) -> Delta {
let mut delta = Delta::default();
if validator.is_active_in_previous_epoch {
if !validator.is_previous_epoch_attester {
delta.penalize(inactivity_penalty);
} else {
// If a validator did attest, apply a small penalty for getting attestations included
// late.
let inclusion = validator
.inclusion_info
.expect("It is a logic error for an attester not to have an inclusion distance.");
delta.reward(
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
);
delta.penalize(base_reward);
}
if !validator.is_previous_epoch_boundary_attester {
delta.reward(inactivity_penalty);
}
if !validator.is_previous_epoch_head_attester {
delta.penalize(inactivity_penalty);
}
}
// Penalize slashed-but-inactive validators as though they were active but offline.
if !validator.is_active_in_previous_epoch
& validator.is_slashed
& !validator.is_withdrawable_in_current_epoch
{
delta.penalize(2 * inactivity_penalty + base_reward);
}
delta
}
/// Calculate the deltas based upon the winning roots for attestations during the previous epoch.
///
/// Spec v0.5.0
fn get_crosslink_deltas(
deltas: &mut Vec<Delta>,
state: &BeaconState,
validator_statuses: &ValidatorStatuses,
spec: &ChainSpec,
) -> Result<(), Error> {
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
let mut delta = Delta::default();
let base_reward = get_base_reward(
state,
index,
validator_statuses.total_balances.previous_epoch,
spec,
)?;
if let Some(ref winning_root) = validator.winning_root_info {
delta.reward(
base_reward * winning_root.total_attesting_balance
/ winning_root.total_committee_balance,
);
} else {
delta.penalize(base_reward);
}
deltas[index] += delta;
}
Ok(())
}
/// Returns the base reward for some validator.
///
/// Spec v0.5.0
fn get_base_reward(
state: &BeaconState,
index: usize,
previous_total_balance: u64,
spec: &ChainSpec,
) -> Result<u64, BeaconStateError> {
if previous_total_balance == 0 {
Ok(0)
} else {
let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient;
Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5)
}
}
/// Returns the inactivity penalty for some validator.
///
/// Spec v0.5.0
fn get_inactivity_penalty(
state: &BeaconState,
index: usize,
epochs_since_finality: u64,
previous_total_balance: u64,
spec: &ChainSpec,
) -> Result<u64, BeaconStateError> {
Ok(get_base_reward(state, index, previous_total_balance, spec)?
+ state.get_effective_balance(index, spec)? * epochs_since_finality
/ spec.inactivity_penalty_quotient
/ 2)
}
/// Returns the epochs since the last finalized epoch.
///
/// Spec v0.5.0
fn epochs_since_finality(state: &BeaconState, spec: &ChainSpec) -> Epoch {
state.current_epoch(spec) + 1 - state.finalized_epoch
}

View File

@ -1,133 +0,0 @@
use fnv::FnvHashSet;
use types::*;
/// A set of validator indices, along with the total balance of all those attesters.
#[derive(Default)]
pub struct Attesters {
/// A set of validator indices.
pub indices: FnvHashSet<usize>,
/// The total balance of all validators in `self.indices`.
pub balance: u64,
}
impl Attesters {
/// Add the given indices to the set, incrementing the sets balance by the provided balance.
fn add(&mut self, additional_indices: &[usize], additional_balance: u64) {
self.indices.reserve(additional_indices.len());
for i in additional_indices {
self.indices.insert(*i);
}
self.balance = self.balance.saturating_add(additional_balance);
}
}
/// A collection of `Attester` objects, representing set of attesters that are rewarded/penalized
/// during an epoch transition.
pub struct AttesterSets {
/// All validators who attested during the state's current epoch.
pub current_epoch: Attesters,
/// All validators who attested that the beacon block root of the first slot of the state's
/// current epoch is the same as the one stored in this state.
///
/// In short validators who agreed with the state about the first slot of the current epoch.
pub current_epoch_boundary: Attesters,
/// All validators who attested during the state's previous epoch.
pub previous_epoch: Attesters,
/// All validators who attested that the beacon block root of the first slot of the state's
/// previous epoch is the same as the one stored in this state.
///
/// In short, validators who agreed with the state about the first slot of the previous epoch.
pub previous_epoch_boundary: Attesters,
/// All validators who attested that the beacon block root at the pending attestation's slot is
/// the same as the one stored in this state.
///
/// In short, validators who agreed with the state about the current beacon block root when
/// they attested.
pub previous_epoch_head: Attesters,
}
impl AttesterSets {
/// Loop through all attestations in the state and instantiate a complete `AttesterSets` struct.
///
/// Spec v0.4.0
pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result<Self, BeaconStateError> {
let mut current_epoch = Attesters::default();
let mut current_epoch_boundary = Attesters::default();
let mut previous_epoch = Attesters::default();
let mut previous_epoch_boundary = Attesters::default();
let mut previous_epoch_head = Attesters::default();
for a in &state.latest_attestations {
let attesting_indices =
state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?;
let attesting_balance = state.get_total_balance(&attesting_indices, spec);
if is_from_epoch(a, state.current_epoch(spec), spec) {
current_epoch.add(&attesting_indices, attesting_balance);
if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? {
current_epoch_boundary.add(&attesting_indices, attesting_balance);
}
} else if is_from_epoch(a, state.previous_epoch(spec), spec) {
previous_epoch.add(&attesting_indices, attesting_balance);
if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? {
previous_epoch_boundary.add(&attesting_indices, attesting_balance);
}
if has_common_beacon_block_root(a, state, spec)? {
previous_epoch_head.add(&attesting_indices, attesting_balance);
}
}
}
Ok(Self {
current_epoch,
current_epoch_boundary,
previous_epoch,
previous_epoch_boundary,
previous_epoch_head,
})
}
}
/// Returns `true` if some `PendingAttestation` is from the supplied `epoch`.
///
/// Spec v0.4.0
fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool {
a.data.slot.epoch(spec.slots_per_epoch) == epoch
}
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
/// the first slot of the given epoch.
///
/// Spec v0.4.0
fn has_common_epoch_boundary_root(
a: &PendingAttestation,
state: &BeaconState,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
let slot = epoch.start_slot(spec.slots_per_epoch);
let state_boundary_root = *state
.get_block_root(slot, spec)
.ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?;
Ok(a.data.epoch_boundary_root == state_boundary_root)
}
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
/// the current slot of the `PendingAttestation`.
///
/// Spec v0.4.0
fn has_common_beacon_block_root(
a: &PendingAttestation,
state: &BeaconState,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
let state_block_root = *state
.get_block_root(a.data.slot, spec)
.ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?;
Ok(a.data.beacon_block_root == state_block_root)
}

View File

@ -8,6 +8,8 @@ pub enum EpochProcessingError {
NoRandaoSeed,
PreviousTotalBalanceIsZero,
InclusionDistanceZero,
ValidatorStatusesInconsistent,
DeltasInconsistent,
/// Unable to get the inclusion distance for a validator that should have an inclusion
/// distance. This indicates an internal inconsistency.
///

View File

@ -0,0 +1,38 @@
use crate::common::verify_bitfield_length;
use types::*;
/// Returns validator indices which participated in the attestation.
///
/// Spec v0.5.0
pub fn get_attestation_participants(
state: &BeaconState,
attestation_data: &AttestationData,
bitfield: &Bitfield,
spec: &ChainSpec,
) -> Result<Vec<usize>, BeaconStateError> {
let epoch = attestation_data.slot.epoch(spec.slots_per_epoch);
let crosslink_committee =
state.get_crosslink_committee_for_shard(epoch, attestation_data.shard, spec)?;
if crosslink_committee.slot != attestation_data.slot {
return Err(BeaconStateError::NoCommitteeForShard);
}
let committee = &crosslink_committee.committee;
if !verify_bitfield_length(&bitfield, committee.len()) {
return Err(BeaconStateError::InvalidBitfield);
}
let mut participants = Vec::with_capacity(committee.len());
for (i, validator_index) in committee.iter().enumerate() {
match bitfield.get(i) {
Ok(bit) if bit => participants.push(*validator_index),
_ => {}
}
}
participants.shrink_to_fit();
Ok(participants)
}

View File

@ -1,12 +1,11 @@
use super::errors::InclusionError;
use super::get_attestation_participants::get_attestation_participants;
use types::*;
/// Returns the distance between the first included attestation for some validator and this
/// slot.
///
/// Note: In the spec this is defined "inline", not as a helper function.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn inclusion_distance(
state: &BeaconState,
attestations: &[&PendingAttestation],
@ -19,9 +18,7 @@ pub fn inclusion_distance(
/// Returns the slot of the earliest included attestation for some validator.
///
/// Note: In the spec this is defined "inline", not as a helper function.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn inclusion_slot(
state: &BeaconState,
attestations: &[&PendingAttestation],
@ -34,9 +31,7 @@ pub fn inclusion_slot(
/// Finds the earliest included attestation for some validator.
///
/// Note: In the spec this is defined "inline", not as a helper function.
///
/// Spec v0.4.0
/// Spec v0.5.0
fn earliest_included_attestation(
state: &BeaconState,
attestations: &[&PendingAttestation],
@ -47,7 +42,7 @@ fn earliest_included_attestation(
for (i, a) in attestations.iter().enumerate() {
let participants =
state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?;
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
if participants.iter().any(|i| *i == validator_index) {
included_attestations.push(i);
}

View File

@ -0,0 +1,28 @@
use crate::common::exit_validator;
use types::{BeaconStateError as Error, *};
/// Iterate through the validator registry and eject active validators with balance below
/// ``EJECTION_BALANCE``.
///
/// Spec v0.5.0
pub fn process_ejections(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
// There is an awkward double (triple?) loop here because we can't loop across the borrowed
// active validator indices and mutate state in the one loop.
let exitable: Vec<usize> = state
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
.iter()
.filter_map(|&i| {
if state.validator_balances[i as usize] < spec.ejection_balance {
Some(i)
} else {
None
}
})
.collect();
for validator_index in exitable {
exit_validator(state, validator_index, spec)?
}
Ok(())
}

View File

@ -0,0 +1,42 @@
use types::*;
/// Process the exit queue.
///
/// Spec v0.5.0
pub fn process_exit_queue(state: &mut BeaconState, spec: &ChainSpec) {
let current_epoch = state.current_epoch(spec);
let eligible = |index: usize| {
let validator = &state.validator_registry[index];
if validator.withdrawable_epoch != spec.far_future_epoch {
false
} else {
current_epoch >= validator.exit_epoch + spec.min_validator_withdrawability_delay
}
};
let mut eligable_indices: Vec<usize> = (0..state.validator_registry.len())
.filter(|i| eligible(*i))
.collect();
eligable_indices.sort_by_key(|i| state.validator_registry[*i].exit_epoch);
for (dequeues, index) in eligable_indices.iter().enumerate() {
if dequeues as u64 >= spec.max_exit_dequeues_per_epoch {
break;
}
prepare_validator_for_withdrawal(state, *index, spec);
}
}
/// Initiate an exit for the validator of the given `index`.
///
/// Spec v0.5.0
fn prepare_validator_for_withdrawal(
state: &mut BeaconState,
validator_index: usize,
spec: &ChainSpec,
) {
state.validator_registry[validator_index].withdrawable_epoch =
state.current_epoch(spec) + spec.min_validator_withdrawability_delay;
}

View File

@ -0,0 +1,35 @@
use types::{BeaconStateError as Error, *};
/// Process slashings.
///
/// Spec v0.5.0
pub fn process_slashings(
state: &mut BeaconState,
current_total_balance: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec);
let total_at_start = state.get_slashed_balance(current_epoch + 1, spec)?;
let total_at_end = state.get_slashed_balance(current_epoch, spec)?;
let total_penalities = total_at_end - total_at_start;
for (index, validator) in state.validator_registry.iter().enumerate() {
let should_penalize = current_epoch.as_usize()
== validator.withdrawable_epoch.as_usize() - spec.latest_slashed_exit_length / 2;
if validator.slashed && should_penalize {
let effective_balance = state.get_effective_balance(index, spec)?;
let penalty = std::cmp::max(
effective_balance * std::cmp::min(total_penalities * 3, current_total_balance)
/ current_total_balance,
effective_balance / spec.min_penalty_quotient,
);
state.validator_balances[index] -= penalty;
}
}
Ok(())
}

View File

@ -0,0 +1,150 @@
use super::super::common::exit_validator;
use super::Error;
use types::*;
/// Peforms a validator registry update, if required.
///
/// Spec v0.5.0
pub fn update_registry_and_shuffling_data(
state: &mut BeaconState,
current_total_balance: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
// First set previous shuffling data to current shuffling data.
state.previous_shuffling_epoch = state.current_shuffling_epoch;
state.previous_shuffling_start_shard = state.previous_shuffling_start_shard;
state.previous_shuffling_seed = state.previous_shuffling_seed;
let current_epoch = state.current_epoch(spec);
let next_epoch = current_epoch + 1;
// Check we should update, and if so, update.
if should_update_validator_registry(state, spec)? {
update_validator_registry(state, current_total_balance, spec)?;
// If we update the registry, update the shuffling data and shards as well.
state.current_shuffling_epoch = next_epoch;
state.current_shuffling_start_shard = {
let active_validators =
state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?;
let epoch_committee_count = spec.get_epoch_committee_count(active_validators.len());
(state.current_shuffling_start_shard + epoch_committee_count) % spec.shard_count
};
state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)?;
} else {
// If processing at least on crosslink keeps failing, the reshuffle every power of two, but
// don't update the current_shuffling_start_shard.
let epochs_since_last_update = current_epoch - state.validator_registry_update_epoch;
if epochs_since_last_update > 1 && epochs_since_last_update.is_power_of_two() {
state.current_shuffling_epoch = next_epoch;
state.current_shuffling_seed =
state.generate_seed(state.current_shuffling_epoch, spec)?;
}
}
Ok(())
}
/// Returns `true` if the validator registry should be updated during an epoch processing.
///
/// Spec v0.5.0
pub fn should_update_validator_registry(
state: &BeaconState,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
if state.finalized_epoch <= state.validator_registry_update_epoch {
return Ok(false);
}
let num_active_validators = state
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
.len();
let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators);
for shard in (0..current_epoch_committee_count)
.map(|i| (state.current_shuffling_start_shard + i as u64) % spec.shard_count)
{
if state.latest_crosslinks[shard as usize].epoch <= state.validator_registry_update_epoch {
return Ok(false);
}
}
Ok(true)
}
/// Update validator registry, activating/exiting validators if possible.
///
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
///
/// Spec v0.5.0
pub fn update_validator_registry(
state: &mut BeaconState,
current_total_balance: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
let current_epoch = state.current_epoch(spec);
let max_balance_churn = std::cmp::max(
spec.max_deposit_amount,
current_total_balance / (2 * spec.max_balance_churn_quotient),
);
// Activate validators within the allowable balance churn.
let mut balance_churn = 0;
for index in 0..state.validator_registry.len() {
let not_activated =
state.validator_registry[index].activation_epoch == spec.far_future_epoch;
let has_enough_balance = state.validator_balances[index] >= spec.max_deposit_amount;
if not_activated && has_enough_balance {
// Check the balance churn would be within the allowance.
balance_churn += state.get_effective_balance(index, spec)?;
if balance_churn > max_balance_churn {
break;
}
activate_validator(state, index, false, spec);
}
}
// Exit validators within the allowable balance churn.
let mut balance_churn = 0;
for index in 0..state.validator_registry.len() {
let not_exited = state.validator_registry[index].exit_epoch == spec.far_future_epoch;
let has_initiated_exit = state.validator_registry[index].initiated_exit;
if not_exited && has_initiated_exit {
// Check the balance churn would be within the allowance.
balance_churn += state.get_effective_balance(index, spec)?;
if balance_churn > max_balance_churn {
break;
}
exit_validator(state, index, spec)?;
}
}
state.validator_registry_update_epoch = current_epoch;
Ok(())
}
/// Activate the validator of the given ``index``.
///
/// Spec v0.5.0
pub fn activate_validator(
state: &mut BeaconState,
validator_index: usize,
is_genesis: bool,
spec: &ChainSpec,
) {
let current_epoch = state.current_epoch(spec);
state.validator_registry[validator_index].activation_epoch = if is_genesis {
spec.genesis_epoch
} else {
state.get_delayed_activation_exit_epoch(current_epoch, spec)
}
}

View File

@ -0,0 +1,340 @@
use super::get_attestation_participants::get_attestation_participants;
use super::WinningRootHashSet;
use types::*;
/// Sets the boolean `var` on `self` to be true if it is true on `other`. Otherwise leaves `self`
/// as is.
macro_rules! set_self_if_other_is_true {
($self_: ident, $other: ident, $var: ident) => {
if $other.$var {
$self_.$var = true;
}
};
}
/// The information required to reward some validator for their participation in a "winning"
/// crosslink root.
#[derive(Default, Clone)]
pub struct WinningRootInfo {
/// The total balance of the crosslink committee.
pub total_committee_balance: u64,
/// The total balance of the crosslink committee that attested for the "winning" root.
pub total_attesting_balance: u64,
}
/// The information required to reward a block producer for including an attestation in a block.
#[derive(Clone, Copy)]
pub struct InclusionInfo {
/// The earliest slot a validator had an attestation included in the previous epoch.
pub slot: Slot,
/// The distance between the attestation slot and the slot that attestation was included in a
/// block.
pub distance: Slot,
/// The index of the proposer at the slot where the attestation was included.
pub proposer_index: usize,
}
impl Default for InclusionInfo {
/// Defaults to `slot` and `distance` at their maximum values and `proposer_index` at zero.
fn default() -> Self {
Self {
slot: Slot::max_value(),
distance: Slot::max_value(),
proposer_index: 0,
}
}
}
impl InclusionInfo {
/// Tests if some `other` `InclusionInfo` has a lower inclusion slot than `self`. If so,
/// replaces `self` with `other`.
pub fn update(&mut self, other: &Self) {
if other.slot < self.slot {
self.slot = other.slot;
self.distance = other.distance;
self.proposer_index = other.proposer_index;
}
}
}
/// Information required to reward some validator during the current and previous epoch.
#[derive(Default, Clone)]
pub struct ValidatorStatus {
/// True if the validator has been slashed, ever.
pub is_slashed: bool,
/// True if the validator can withdraw in the current epoch.
pub is_withdrawable_in_current_epoch: bool,
/// True if the validator was active in the state's _current_ epoch.
pub is_active_in_current_epoch: bool,
/// True if the validator was active in the state's _previous_ epoch.
pub is_active_in_previous_epoch: bool,
/// True if the validator had an attestation included in the _current_ epoch.
pub is_current_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _current_
/// epoch matches the block root known to the state.
pub is_current_epoch_boundary_attester: bool,
/// True if the validator had an attestation included in the _previous_ epoch.
pub is_previous_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _previous_
/// epoch matches the block root known to the state.
pub is_previous_epoch_boundary_attester: bool,
/// True if the validator's beacon block root attestation in the _previous_ epoch at the
/// attestation's slot (`attestation_data.slot`) matches the block root known to the state.
pub is_previous_epoch_head_attester: bool,
/// Information used to reward the block producer of this validators earliest-included
/// attestation.
pub inclusion_info: Option<InclusionInfo>,
/// Information used to reward/penalize the validator if they voted in the super-majority for
/// some shard block.
pub winning_root_info: Option<WinningRootInfo>,
}
impl ValidatorStatus {
/// Accepts some `other` `ValidatorStatus` and updates `self` if required.
///
/// Will never set one of the `bool` fields to `false`, it will only set it to `true` if other
/// contains a `true` field.
///
/// Note: does not update the winning root info, this is done manually.
pub fn update(&mut self, other: &Self) {
// Update all the bool fields, only updating `self` if `other` is true (never setting
// `self` to false).
set_self_if_other_is_true!(self, other, is_slashed);
set_self_if_other_is_true!(self, other, is_withdrawable_in_current_epoch);
set_self_if_other_is_true!(self, other, is_active_in_current_epoch);
set_self_if_other_is_true!(self, other, is_active_in_previous_epoch);
set_self_if_other_is_true!(self, other, is_current_epoch_attester);
set_self_if_other_is_true!(self, other, is_current_epoch_boundary_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_boundary_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester);
if let Some(other_info) = other.inclusion_info {
if let Some(self_info) = self.inclusion_info.as_mut() {
self_info.update(&other_info);
} else {
self.inclusion_info = other.inclusion_info;
}
}
}
}
/// The total effective balances for different sets of validators during the previous and current
/// epochs.
#[derive(Default, Clone)]
pub struct TotalBalances {
/// The total effective balance of all active validators during the _current_ epoch.
pub current_epoch: u64,
/// The total effective balance of all active validators during the _previous_ epoch.
pub previous_epoch: u64,
/// The total effective balance of all validators who attested during the _current_ epoch.
pub current_epoch_attesters: u64,
/// The total effective balance of all validators who attested during the _current_ epoch and
/// agreed with the state about the beacon block at the first slot of the _current_ epoch.
pub current_epoch_boundary_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch.
pub previous_epoch_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the first slot of the _previous_ epoch.
pub previous_epoch_boundary_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the time of attestation.
pub previous_epoch_head_attesters: u64,
}
/// Summarised information about validator participation in the _previous and _current_ epochs of
/// some `BeaconState`.
#[derive(Clone)]
pub struct ValidatorStatuses {
/// Information about each individual validator from the state's validator registy.
pub statuses: Vec<ValidatorStatus>,
/// Summed balances for various sets of validators.
pub total_balances: TotalBalances,
}
impl ValidatorStatuses {
/// Initializes a new instance, determining:
///
/// - Active validators
/// - Total balances for the current and previous epochs.
///
/// Spec v0.5.0
pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result<Self, BeaconStateError> {
let mut statuses = Vec::with_capacity(state.validator_registry.len());
let mut total_balances = TotalBalances::default();
for (i, validator) in state.validator_registry.iter().enumerate() {
let mut status = ValidatorStatus {
is_slashed: validator.slashed,
is_withdrawable_in_current_epoch: validator
.is_withdrawable_at(state.current_epoch(spec)),
..ValidatorStatus::default()
};
if validator.is_active_at(state.current_epoch(spec)) {
status.is_active_in_current_epoch = true;
total_balances.current_epoch += state.get_effective_balance(i, spec)?;
}
if validator.is_active_at(state.previous_epoch(spec)) {
status.is_active_in_previous_epoch = true;
total_balances.previous_epoch += state.get_effective_balance(i, spec)?;
}
statuses.push(status);
}
Ok(Self {
statuses,
total_balances,
})
}
/// Process some attestations from the given `state` updating the `statuses` and
/// `total_balances` fields.
///
/// Spec v0.5.0
pub fn process_attestations(
&mut self,
state: &BeaconState,
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
for a in state
.previous_epoch_attestations
.iter()
.chain(state.current_epoch_attestations.iter())
{
let attesting_indices =
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
let attesting_balance = state.get_total_balance(&attesting_indices, spec)?;
let mut status = ValidatorStatus::default();
// Profile this attestation, updating the total balances and generating an
// `ValidatorStatus` object that applies to all participants in the attestation.
if is_from_epoch(a, state.current_epoch(spec), spec) {
self.total_balances.current_epoch_attesters += attesting_balance;
status.is_current_epoch_attester = true;
if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? {
self.total_balances.current_epoch_boundary_attesters += attesting_balance;
status.is_current_epoch_boundary_attester = true;
}
} else if is_from_epoch(a, state.previous_epoch(spec), spec) {
self.total_balances.previous_epoch_attesters += attesting_balance;
status.is_previous_epoch_attester = true;
// The inclusion slot and distance are only required for previous epoch attesters.
let relative_epoch = RelativeEpoch::from_slot(state.slot, a.data.slot, spec)?;
status.inclusion_info = Some(InclusionInfo {
slot: a.inclusion_slot,
distance: inclusion_distance(a),
proposer_index: state.get_beacon_proposer_index(
a.inclusion_slot,
relative_epoch,
spec,
)?,
});
if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? {
self.total_balances.previous_epoch_boundary_attesters += attesting_balance;
status.is_previous_epoch_boundary_attester = true;
}
if has_common_beacon_block_root(a, state, spec)? {
self.total_balances.previous_epoch_head_attesters += attesting_balance;
status.is_previous_epoch_head_attester = true;
}
}
// Loop through the participating validator indices and update the status vec.
for validator_index in attesting_indices {
self.statuses[validator_index].update(&status);
}
}
Ok(())
}
/// Update the `statuses` for each validator based upon whether or not they attested to the
/// "winning" shard block root for the previous epoch.
///
/// Spec v0.5.0
pub fn process_winning_roots(
&mut self,
state: &BeaconState,
winning_roots: &WinningRootHashSet,
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
// Loop through each slot in the previous epoch.
for slot in state.previous_epoch(spec).slot_iter(spec.slots_per_epoch) {
let crosslink_committees_at_slot =
state.get_crosslink_committees_at_slot(slot, spec)?;
// Loop through each committee in the slot.
for c in crosslink_committees_at_slot {
// If there was some winning crosslink root for the committee's shard.
if let Some(winning_root) = winning_roots.get(&c.shard) {
let total_committee_balance = state.get_total_balance(&c.committee, spec)?;
for &validator_index in &winning_root.attesting_validator_indices {
// Take note of the balance information for the winning root, it will be
// used later to calculate rewards for that validator.
self.statuses[validator_index].winning_root_info = Some(WinningRootInfo {
total_committee_balance,
total_attesting_balance: winning_root.total_attesting_balance,
})
}
}
}
}
Ok(())
}
}
/// Returns the distance between when the attestation was created and when it was included in a
/// block.
///
/// Spec v0.5.0
fn inclusion_distance(a: &PendingAttestation) -> Slot {
a.inclusion_slot - a.data.slot
}
/// Returns `true` if some `PendingAttestation` is from the supplied `epoch`.
///
/// Spec v0.5.0
fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool {
a.data.slot.epoch(spec.slots_per_epoch) == epoch
}
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
/// the first slot of the given epoch.
///
/// Spec v0.5.0
fn has_common_epoch_boundary_root(
a: &PendingAttestation,
state: &BeaconState,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
let slot = epoch.start_slot(spec.slots_per_epoch);
let state_boundary_root = *state.get_block_root(slot, spec)?;
Ok(a.data.target_root == state_boundary_root)
}
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
/// the current slot of the `PendingAttestation`.
///
/// Spec v0.5.0
fn has_common_beacon_block_root(
a: &PendingAttestation,
state: &BeaconState,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
let state_block_root = *state.get_block_root(a.data.slot, spec)?;
Ok(a.data.beacon_block_root == state_block_root)
}

View File

@ -1,3 +1,4 @@
use super::get_attestation_participants::get_attestation_participants;
use std::collections::HashSet;
use std::iter::FromIterator;
use types::*;
@ -13,14 +14,14 @@ impl WinningRoot {
/// Returns `true` if `self` is a "better" candidate than `other`.
///
/// A winning root is "better" than another if it has a higher `total_attesting_balance`. Ties
/// are broken by favouring the lower `crosslink_data_root` value.
/// are broken by favouring the higher `crosslink_data_root` value.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn is_better_than(&self, other: &Self) -> bool {
if self.total_attesting_balance > other.total_attesting_balance {
true
} else if self.total_attesting_balance == other.total_attesting_balance {
self.crosslink_data_root < other.crosslink_data_root
self.crosslink_data_root > other.crosslink_data_root
} else {
false
}
@ -33,22 +34,21 @@ impl WinningRoot {
/// The `WinningRoot` object also contains additional fields that are useful in later stages of
/// per-epoch processing.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn winning_root(
state: &BeaconState,
shard: u64,
current_epoch_attestations: &[&PendingAttestation],
previous_epoch_attestations: &[&PendingAttestation],
spec: &ChainSpec,
) -> Result<Option<WinningRoot>, BeaconStateError> {
let mut winning_root: Option<WinningRoot> = None;
let crosslink_data_roots: HashSet<Hash256> = HashSet::from_iter(
previous_epoch_attestations
state
.previous_epoch_attestations
.iter()
.chain(current_epoch_attestations.iter())
.chain(state.current_epoch_attestations.iter())
.filter_map(|a| {
if a.data.shard == shard {
if is_eligible_for_winning_root(state, a, shard) {
Some(a.data.crosslink_data_root)
} else {
None
@ -57,18 +57,17 @@ pub fn winning_root(
);
for crosslink_data_root in crosslink_data_roots {
let attesting_validator_indices = get_attesting_validator_indices(
state,
shard,
current_epoch_attestations,
previous_epoch_attestations,
&crosslink_data_root,
spec,
)?;
let attesting_validator_indices =
get_attesting_validator_indices(state, shard, &crosslink_data_root, spec)?;
let total_attesting_balance: u64 = attesting_validator_indices
.iter()
.fold(0, |acc, i| acc + state.get_effective_balance(*i, spec));
let total_attesting_balance: u64 =
attesting_validator_indices
.iter()
.try_fold(0_u64, |acc, i| {
state
.get_effective_balance(*i, spec)
.and_then(|bal| Ok(acc + bal))
})?;
let candidate = WinningRoot {
crosslink_data_root,
@ -88,25 +87,36 @@ pub fn winning_root(
Ok(winning_root)
}
/// Returns all indices which voted for a given crosslink. May contain duplicates.
/// Returns `true` if pending attestation `a` is eligible to become a winning root.
///
/// Spec v0.4.0
/// Spec v0.5.0
fn is_eligible_for_winning_root(state: &BeaconState, a: &PendingAttestation, shard: Shard) -> bool {
if shard >= state.latest_crosslinks.len() as u64 {
return false;
}
a.data.previous_crosslink == state.latest_crosslinks[shard as usize]
}
/// Returns all indices which voted for a given crosslink. Does not contain duplicates.
///
/// Spec v0.5.0
fn get_attesting_validator_indices(
state: &BeaconState,
shard: u64,
current_epoch_attestations: &[&PendingAttestation],
previous_epoch_attestations: &[&PendingAttestation],
crosslink_data_root: &Hash256,
spec: &ChainSpec,
) -> Result<Vec<usize>, BeaconStateError> {
let mut indices = vec![];
for a in current_epoch_attestations
for a in state
.current_epoch_attestations
.iter()
.chain(previous_epoch_attestations.iter())
.chain(state.previous_epoch_attestations.iter())
{
if (a.data.shard == shard) && (a.data.crosslink_data_root == *crosslink_data_root) {
indices.append(&mut state.get_attestation_participants(
indices.append(&mut get_attestation_participants(
state,
&a.data,
&a.aggregation_bitfield,
spec,
@ -114,5 +124,41 @@ fn get_attesting_validator_indices(
}
}
// Sort the list (required for dedup). "Unstable" means the sort may re-order equal elements,
// this causes no issue here.
//
// These sort + dedup ops are potentially good CPU time optimisation targets.
indices.sort_unstable();
// Remove all duplicate indices (requires a sorted list).
indices.dedup();
Ok(indices)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_better_than() {
let worse = WinningRoot {
crosslink_data_root: Hash256::from_slice(&[1; 32]),
attesting_validator_indices: vec![],
total_attesting_balance: 42,
};
let better = WinningRoot {
crosslink_data_root: Hash256::from_slice(&[2; 32]),
..worse.clone()
};
assert!(better.is_better_than(&worse));
let better = WinningRoot {
total_attesting_balance: worse.total_attesting_balance + 1,
..worse.clone()
};
assert!(better.is_better_than(&worse));
}
}

View File

@ -1,5 +1,6 @@
use crate::*;
use types::{BeaconState, BeaconStateError, ChainSpec, Hash256};
use ssz::TreeHash;
use types::*;
#[derive(Debug, PartialEq)]
pub enum Error {
@ -9,12 +10,14 @@ pub enum Error {
/// Advances a state forward by one slot, performing per-epoch processing if required.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn per_slot_processing(
state: &mut BeaconState,
previous_block_root: Hash256,
latest_block_header: &BeaconBlockHeader,
spec: &ChainSpec,
) -> Result<(), Error> {
cache_state(state, latest_block_header, spec)?;
if (state.slot + 1) % spec.slots_per_epoch == 0 {
per_epoch_processing(state, spec)?;
state.advance_caches();
@ -22,27 +25,35 @@ pub fn per_slot_processing(
state.slot += 1;
update_block_roots(state, previous_block_root, spec);
Ok(())
}
/// Updates the state's block roots as per-slot processing is performed.
///
/// Spec v0.4.0
pub fn update_block_roots(state: &mut BeaconState, previous_block_root: Hash256, spec: &ChainSpec) {
state.latest_block_roots[(state.slot.as_usize() - 1) % spec.latest_block_roots_length] =
previous_block_root;
fn cache_state(
state: &mut BeaconState,
latest_block_header: &BeaconBlockHeader,
spec: &ChainSpec,
) -> Result<(), Error> {
let previous_slot_state_root = Hash256::from_slice(&state.hash_tree_root()[..]);
if state.slot.as_usize() % spec.latest_block_roots_length == 0 {
let root = merkle_root(&state.latest_block_roots[..]);
state.batched_block_roots.push(root);
// Note: increment the state slot here to allow use of our `state_root` and `block_root`
// getter/setter functions.
//
// This is a bit hacky, however it gets the job safely without lots of code.
let previous_slot = state.slot;
state.slot += 1;
// Store the previous slot's post-state transition root.
if state.latest_block_header.state_root == spec.zero_hash {
state.latest_block_header.state_root = previous_slot_state_root
}
}
fn merkle_root(_input: &[Hash256]) -> Hash256 {
// TODO: implement correctly.
Hash256::zero()
let latest_block_root = Hash256::from_slice(&latest_block_header.hash_tree_root()[..]);
state.set_block_root(previous_slot, latest_block_root, spec)?;
// Set the state slot back to what it should be.
state.slot -= 1;
Ok(())
}
impl From<BeaconStateError> for Error {

View File

@ -0,0 +1,42 @@
use serde_derive::Deserialize;
use types::*;
#[derive(Debug, Deserialize)]
pub struct TestCase {
pub name: String,
pub config: ChainSpec,
pub verify_signatures: bool,
pub initial_state: BeaconState,
pub blocks: Vec<BeaconBlock>,
}
#[derive(Debug, Deserialize)]
pub struct TestDoc {
pub title: String,
pub summary: String,
pub fork: String,
pub version: String,
pub test_cases: Vec<TestCase>,
}
#[test]
#[ignore]
fn yaml() {
use serde_yaml;
use std::{fs::File, io::prelude::*, path::PathBuf};
let mut file = {
let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
file_path_buf.push("specs/example.yml");
File::open(file_path_buf).unwrap()
};
let mut yaml_str = String::new();
file.read_to_string(&mut yaml_str).unwrap();
let yaml_str = yaml_str.to_lowercase();
let _doc: TestDoc = serde_yaml::from_str(&yaml_str.as_str()).unwrap();
}

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