Merge current master and fix ssz-fuzzing

This commit is contained in:
Kirk Baird 2019-03-26 14:53:50 +11:00
commit 116d995960
No known key found for this signature in database
GPG Key ID: BF864B7ED0BEA33F
190 changed files with 7696 additions and 3882 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,8 +20,14 @@ 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",
"validator_client",
"account_manager",
]

View File

@ -0,0 +1,13 @@
[package]
name = "account_manager"
version = "0.0.1"
authors = ["Luke Anderson <luke@sigmaprime.io>"]
edition = "2018"
[dependencies]
bls = { path = "../eth2/utils/bls" }
clap = "2.32.0"
slog = "^2.2.3"
slog-term = "^2.4.0"
slog-async = "^2.3.0"
validator_client = { path = "../validator_client" }

24
account_manager/README.md Normal file
View File

@ -0,0 +1,24 @@
# Lighthouse Accounts Manager
The accounts manager (AM) is a stand-alone binary which allows
users to generate and manage the cryptographic keys necessary to
interact with Ethereum Serenity.
## Roles
The AM is responsible for the following tasks:
- Generation of cryptographic key pairs
- Must acquire sufficient entropy to ensure keys are generated securely (TBD)
- Secure storage of private keys
- Keys must be encrypted while at rest on the disk (TBD)
- The format is compatible with the validator client
- Produces messages and transactions necessary to initiate
staking on Ethereum 1.x (TPD)
## Implementation
The AM is not a service, and does not run continuously, nor does it
interact with any running services.
It is intended to be executed separately from other Lighthouse binaries
and produce files which can be consumed by them.

View File

@ -0,0 +1,58 @@
use bls::Keypair;
use clap::{App, Arg, SubCommand};
use slog::{debug, info, o, Drain};
use std::path::PathBuf;
use validator_client::Config as ValidatorClientConfig;
fn main() {
// Logging
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!());
// CLI
let matches = App::new("Lighthouse Accounts Manager")
.version("0.0.1")
.author("Sigma Prime <contact@sigmaprime.io>")
.about("Eth 2.0 Accounts Manager")
.arg(
Arg::with_name("datadir")
.long("datadir")
.value_name("DIR")
.help("Data directory for keys and databases.")
.takes_value(true),
)
.subcommand(
SubCommand::with_name("generate")
.about("Generates a new validator private key")
.version("0.0.1")
.author("Sigma Prime <contact@sigmaprime.io>"),
)
.get_matches();
let config = ValidatorClientConfig::parse_args(&matches, &log)
.expect("Unable to build a configuration for the account manager.");
// Log configuration
info!(log, "";
"data_dir" => &config.data_dir.to_str());
match matches.subcommand() {
("generate", Some(_gen_m)) => {
let keypair = Keypair::random();
let key_path: PathBuf = config
.save_key(&keypair)
.expect("Unable to save newly generated private key.");
debug!(
log,
"Keypair generated {:?}, saved to: {:?}",
keypair.identifier(),
key_path.to_string_lossy()
);
}
_ => panic!(
"The account manager must be run with a subcommand. See help for more information."
),
}
}

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,10 +85,8 @@ 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,
) {
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)
}
@ -100,20 +97,10 @@ impl AttestationAggregator {
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,7 +164,11 @@ impl AttestationAggregator {
) -> Vec<Attestation> {
let mut known_attestation_data: HashSet<AttestationData> = HashSet::new();
state.latest_attestations.iter().for_each(|attestation| {
state
.previous_epoch_attestations
.iter()
.chain(state.current_epoch_attestations.iter())
.for_each(|attestation| {
known_attestation_data.insert(attestation.data.clone());
});

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),
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,
)
.ok_or_else(|| Error::BadRecentBlockRoots)?;
)?;
let epoch_boundary_root = *self
.state
let target_root = *self.state.read().get_block_root(
self.state
.read()
.get_block_root(
self.state.read().current_epoch_start_slot(&self.spec),
.slot
.epoch(self.spec.slots_per_epoch)
.start_slot(self.spec.slots_per_epoch),
&self.spec,
)
.ok_or_else(|| Error::BadRecentBlockRoots)?;
)?;
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.
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(),
},
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
body: BeaconBlockBody {
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

@ -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

@ -46,8 +46,8 @@ impl BeaconChainHarness {
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
let (genesis_state, keypairs) = state_builder.build();
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(
@ -127,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> =
@ -233,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

@ -3,12 +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 types::*;
use types::test_utils::{TestingAttesterSlashingBuilder, TestingProposerSlashingBuilder};
use types::test_utils::*;
use yaml_rust::Yaml;
mod config;
@ -63,6 +62,10 @@ impl TestCase {
spec.slots_per_epoch = n;
}
if let Some(n) = self.config.persistent_committee_period {
spec.persistent_committee_period = n;
}
spec
}
@ -222,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`.
@ -255,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

@ -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::decode;
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 = decode::<BeaconBlock>(&ssz).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));
@ -228,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);
@ -240,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::decode;
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 = decode::<BeaconState>(&ssz).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,156 @@
use crate::rpc::{RPCEvent, RPCMessage, Rpc};
use crate::NetworkConfig;
use futures::prelude::*;
use libp2p::{
core::{
swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess},
PublicKey,
},
gossipsub::{Gossipsub, GossipsubEvent},
identify::{protocol::IdentifyInfo, Identify, IdentifyEvent},
ping::{Ping, PingEvent},
tokio_io::{AsyncRead, AsyncWrite},
NetworkBehaviour, PeerId,
};
use slog::{debug, o};
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> {
/// The routing pub-sub mechanism for eth2.
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>,
/// Allows discovery of IP addresses for peers on the network.
identify: Identify<TSubstream>,
/// Keep regular connection to peers and disconnect if absent.
// TODO: Keepalive, likely remove this later.
// TODO: Make the ping time customizeable.
ping: Ping<TSubstream>,
#[behaviour(ignore)]
events: Vec<BehaviourEvent>,
/// Logger for behaviour actions.
#[behaviour(ignore)]
log: slog::Logger,
}
// 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> NetworkBehaviourEventProcess<IdentifyEvent>
for Behaviour<TSubstream>
{
fn inject_event(&mut self, event: IdentifyEvent) {
match event {
IdentifyEvent::Identified {
peer_id, mut info, ..
} => {
if info.listen_addrs.len() > 20 {
debug!(
self.log,
"More than 20 peers have been identified, truncating"
);
info.listen_addrs.truncate(20);
}
self.events.push(BehaviourEvent::Identified(peer_id, info));
}
IdentifyEvent::Error { .. } => {}
IdentifyEvent::SendBack { .. } => {}
}
}
}
impl<TSubstream: AsyncRead + AsyncWrite> NetworkBehaviourEventProcess<PingEvent>
for Behaviour<TSubstream>
{
fn inject_event(&mut self, _event: PingEvent) {
// not interested in ping responses at the moment.
}
}
impl<TSubstream: AsyncRead + AsyncWrite> Behaviour<TSubstream> {
pub fn new(local_public_key: PublicKey, net_conf: &NetworkConfig, log: &slog::Logger) -> Self {
let local_peer_id = local_public_key.clone().into_peer_id();
let identify_config = net_conf.identify_config.clone();
let behaviour_log = log.new(o!());
Behaviour {
gossipsub: Gossipsub::new(local_peer_id, net_conf.gs_config.clone()),
serenity_rpc: Rpc::new(log),
identify: Identify::new(
identify_config.version,
identify_config.user_agent,
local_public_key,
),
ping: Ping::new(),
events: Vec::new(),
log: behaviour_log,
}
}
/// 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),
Identified(PeerId, IdentifyInfo),
// TODO: This is a stub at the moment
Message(String),
}

View File

@ -0,0 +1,66 @@
use crate::Multiaddr;
use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder};
#[derive(Clone, Debug)]
/// Network configuration for lighthouse.
pub struct Config {
//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,
/// Configuration parameters for node identification protocol.
pub identify_config: IdentifyConfig,
/// List of nodes to initially connect to.
pub boot_nodes: Vec<Multiaddr>,
/// Client version
pub client_version: String,
/// List of topics to subscribe to as strings
pub topics: Vec<String>,
}
impl Default for Config {
/// Generate a default network configuration.
fn default() -> Self {
Config {
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(),
identify_config: IdentifyConfig::default(),
boot_nodes: Vec::new(),
client_version: version::version(),
topics: vec![String::from("beacon_chain")],
}
}
}
impl Config {
pub fn new(boot_nodes: Vec<Multiaddr>) -> Self {
let mut conf = Config::default();
conf.boot_nodes = boot_nodes;
conf
}
}
/// The configuration parameters for the Identify protocol
#[derive(Debug, Clone)]
pub struct IdentifyConfig {
/// The protocol version to listen on.
pub version: String,
/// The client's name and version for identification.
pub user_agent: String,
}
impl Default for IdentifyConfig {
fn default() -> Self {
Self {
version: "/eth/serenity/1.0".to_string(),
user_agent: version::version(),
}
}
}

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;
mod config;
pub mod error;
pub mod rpc;
mod service;
pub use config::Config as NetworkConfig;
pub use libp2p::{
gossipsub::{GossipsubConfig, GossipsubConfigBuilder},
PeerId,
};
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,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,247 @@
use super::methods::*;
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 = 4_194_304; // 4M
/// 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::Goodbye => {
let (goodbye_code, _index) = u64::ssz_decode(&packet, index)?;
RPCRequest::Goodbye(goodbye_code)
}
RPCMethod::BeaconBlockRoots => {
let (block_roots_request, _index) =
BeaconBlockRootsRequest::ssz_decode(&packet, index)?;
RPCRequest::BeaconBlockRoots(block_roots_request)
}
RPCMethod::BeaconBlockHeaders => {
let (block_headers_request, _index) =
BeaconBlockHeadersRequest::ssz_decode(&packet, index)?;
RPCRequest::BeaconBlockHeaders(block_headers_request)
}
RPCMethod::BeaconBlockBodies => {
let (block_bodies_request, _index) =
BeaconBlockBodiesRequest::ssz_decode(&packet, index)?;
RPCRequest::BeaconBlockBodies(block_bodies_request)
}
RPCMethod::BeaconChainState => {
let (chain_state_request, _index) =
BeaconChainStateRequest::ssz_decode(&packet, index)?;
RPCRequest::BeaconChainState(chain_state_request)
}
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::Goodbye => unreachable!("Should never receive a goodbye response"),
RPCMethod::BeaconBlockRoots => {
let (body, _index) = BeaconBlockRootsResponse::ssz_decode(&packet, index)?;
RPCResponse::BeaconBlockRoots(body)
}
RPCMethod::BeaconBlockHeaders => {
let (body, _index) = BeaconBlockHeadersResponse::ssz_decode(&packet, index)?;
RPCResponse::BeaconBlockHeaders(body)
}
RPCMethod::BeaconBlockBodies => {
let (body, _index) = BeaconBlockBodiesResponse::ssz_decode(&packet, index)?;
RPCResponse::BeaconBlockBodies(body)
}
RPCMethod::BeaconChainState => {
let (body, _index) = BeaconChainStateResponse::ssz_decode(&packet, index)?;
RPCResponse::BeaconChainState(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);
}
RPCRequest::Goodbye(body) => {
s.append(body);
}
RPCRequest::BeaconBlockRoots(body) => {
s.append(body);
}
RPCRequest::BeaconBlockHeaders(body) => {
s.append(body);
}
RPCRequest::BeaconBlockBodies(body) => {
s.append(body);
}
RPCRequest::BeaconChainState(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);
}
RPCResponse::BeaconBlockRoots(response) => {
s.append(response);
}
RPCResponse::BeaconBlockHeaders(response) => {
s.append(response);
}
RPCResponse::BeaconBlockBodies(response) => {
s.append(response);
}
RPCResponse::BeaconChainState(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,178 @@
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::identify::protocol::IdentifyInfo;
use libp2p::{core, secio, PeerId, Swarm, Transport};
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");
// 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
// TODO: Save and recover node key from disk
let local_private_key = secio::SecioKeyPair::secp256k1_generated().unwrap();
let local_public_key = local_private_key.to_public_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_public_key.clone(), &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() {
//Behaviour events
Ok(Async::Ready(Some(event))) => match event {
// TODO: Stub here for debugging
BehaviourEvent::Message(m) => {
debug!(self.log, "Message received: {}", m);
return Ok(Async::Ready(Some(Libp2pEvent::Message(m))));
}
BehaviourEvent::RPC(peer_id, event) => {
return Ok(Async::Ready(Some(Libp2pEvent::RPC(peer_id, event))));
}
BehaviourEvent::PeerDialed(peer_id) => {
return Ok(Async::Ready(Some(Libp2pEvent::PeerDialed(peer_id))));
}
BehaviourEvent::Identified(peer_id, info) => {
return Ok(Async::Ready(Some(Libp2pEvent::Identified(peer_id, info))));
}
},
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 {
/// An RPC response request has been received on the swarm.
RPC(PeerId, RPCEvent),
/// Initiated the connection to a new peer.
PeerDialed(PeerId),
/// Received information about a peer on the network.
Identified(PeerId, IdentifyInfo),
// TODO: Pub-sub testing only.
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,186 @@
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 {
//libp2p_service: Arc<Mutex<LibP2PService>>,
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 libp2p service
let libp2p_log = log.new(o!("Service" => "Libp2p"));
let libp2p_service = LibP2PService::new(config.clone(), libp2p_log)?;
// TODO: Spawn thread to handle libp2p messages and pass to message handler thread.
let libp2p_exit = spawn_service(
libp2p_service,
network_recv,
message_handler_send,
executor,
log,
)?;
let network_service = Service {
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(
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(
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 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 libp2p_service.poll() {
Ok(Async::Ready(Some(event))) => match event {
Libp2pEvent::RPC(peer_id, rpc_event) => {
trace!(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")?;
}
Libp2pEvent::PeerDialed(peer_id) => {
debug!(log, "Peer Dialed: {:?}", peer_id);
message_handler_send
.send(HandlerMessage::PeerDialed(peer_id))
.map_err(|_| "failed to send rpc to handler")?;
}
Libp2pEvent::Identified(peer_id, info) => {
debug!(
log,
"We have identified peer: {:?} with {:?}", peer_id, info
);
}
Libp2pEvent::Message(m) => debug!(
libp2p_service.log,
"Network Service: Message received: {}", m
),
},
Ok(Async::Ready(None)) => unreachable!("Stream never ends"),
Ok(Async::NotReady) => break,
Err(_) => 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
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 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;
@ -434,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

@ -96,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,

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

@ -10,10 +10,7 @@ use log::{debug, trace};
use std::cmp::Ordering;
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(
@ -226,17 +221,17 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<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());
@ -280,7 +275,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<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
@ -288,7 +283,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<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 {
@ -314,8 +309,8 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<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;
@ -405,7 +400,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<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

@ -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;
@ -220,13 +215,11 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
head_vote_count = vote_count;
}
// resolve ties - choose smaller hash
else if vote_count == head_vote_count {
if *child_hash < head_hash {
else if vote_count == head_vote_count && *child_hash < head_hash {
head_hash = *child_hash;
}
}
}
}
Ok(head_hash)
}
}

View File

@ -90,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![],
@ -117,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 {
@ -137,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(),
};
@ -242,8 +242,9 @@ fn setup_inital_state(
let spec = ChainSpec::foundation();
let state_builder =
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,

View File

@ -48,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!(

View File

@ -1,23 +1,103 @@
use block_benching_builder::BlockBenchingBuilder;
use criterion::Criterion;
use criterion::{criterion_group, criterion_main};
use env_logger::{Builder, Env};
use log::info;
use types::*;
mod bench_block_processing;
mod bench_epoch_processing;
mod block_benching_builder;
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_epoch_processing::bench_epoch_processing_n_validators(c, 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),
);
}
criterion_group!(benches, state_processing);
/// 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,
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,708 @@
title: Sanity tests -- small config -- 32 validators
summary: Basic sanity checks from phase 0 spec pythonization using a small state configuration and 32 validators.
All tests are run with `verify_signatures` as set to False.
Tests generated via https://github.com/ethereum/research/blob/master/spec_pythonizer/sanity_check.py
test_suite: beacon_state
fork: phase0-0.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: '0x00'
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: '0x00000000'
current_version: '0x00000000'
epoch: 536870912
validator_registry:
- pubkey: '0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000001'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000002'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000003'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000004'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000005'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000006'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000007'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xa85ae765588126f5e860d019c0e26235f567a9c0c0b2d8ff30f3e8d436b1082596e5e7462d20f5be3764fd473e57f9cf'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000008'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x99cdf3807146e68e041314ca93e1fee0991224ec2a74beb2866816fd0826ce7b6263ee31e953a86d1b72cc2215a57793'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000009'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000a'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x80fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a55'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000b'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x8345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000c'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x851f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000d'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x99bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000e'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x8d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a41495083582'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000f'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xa73eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000010'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xb098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000011'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x9252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000012'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xb271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000013'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xa272e9d1d50a4aea7d8f0583948090d0888be5777f2846800b8281139cd4aa9eee05f89b069857a3e77ccfaae1615f9c'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000014'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x9780e853f8ce7eda772c6691d25e220ca1d2ab0db51a7824b700620f7ac94c06639e91c98bb6abd78128f0ec845df8ef'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000015'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xab48aa2cc6f4a0bb63b5d67be54ac3aed10326dda304c5aeb9e942b40d6e7610478377680ab90e092ef1895e62786008'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000016'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x8c8b694b04d98a749a0763c72fc020ef61b2bb3f63ebb182cb2e568f6a8b9ca3ae013ae78317599e7e7ba2a528ec754a'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000017'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x9717182463fbe215168e6762abcbb55c5c65290f2b5a2af616f8a6f50d625b46164178a11622d21913efdfa4b800648d'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000018'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xacb58c81ae0cae2e9d4d446b730922239923c345744eee58efaadb36e9a0925545b18a987acf0bad469035b291e37269'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000019'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x81ccc19e3b938ec2405099e90022a4218baa5082a3ca0974b24be0bc8b07e5fffaed64bef0d02c4dbfb6a307829afc5c'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001a'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xab83dfefb120fab7665a607d749ef1765fbb3cc0ba5827a20a135402c09d987c701ddb5b60f0f5495026817e8ab6ea2e'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001b'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xb6ad11e5d15f77c1143b1697344911b9c590110fdd8dd09df2e58bfd757269169deefe8be3544d4e049fb3776fb0bcfb'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001c'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0x8515e7f61ca0470e165a44d247a23f17f24bf6e37185467bedb7981c1003ea70bbec875703f793dd8d11e56afa7f74ba'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001d'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xad84464b3966ec5bede84aa487facfca7823af383715078da03b387cc2f5d5597cdd7d025aa07db00a38b953bdeb6e3f'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001e'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xb29043a7273d0a2dbc2b747dcf6a5eccbd7ccb44b2d72e985537b117929bc3fd3a99001481327788ad040b4077c47c0d'
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001f'
activation_epoch: 536870912
exit_epoch: 18446744073709551615
withdrawable_epoch: 18446744073709551615
initiated_exit: false
slashed: false
- pubkey: '0xa72841987e4f219d54f2b6a9eac5fe6e78704644753c3579e776a3691bc123743f8c63770ed0f72a71e9e964dbf58f43'
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000020'
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: '0x7a81d831e99dc63f9f10d4abce84c26473d4c2f65ec4acf9000684059473b072'
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:
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
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: '0x13f2001ff0ee4a528b3c43f63d70a997aefca990ed8eada2223ee6ec3807f7cc'
signature: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
historical_roots: []
latest_eth1_data:
deposit_root: '0x826d25bfcb9161aabc799844c5176f7b3444dc5288856f65e0b8060560488912'
block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000'
eth1_data_votes: []
deposit_index: 32
blocks:
- slot: 4294967297
previous_block_root: '0x2befbd4b4fe8c91f3059082c8048e3376a9b7fb309e93044fac32b7cc8849773'
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],
@ -401,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,
@ -423,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],
@ -455,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.
@ -302,9 +309,7 @@ pub enum DepositValidationError {
#[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
@ -334,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 the justified epoch and root is correct.
if attestation_epoch >= state_epoch {
verify!(
state.slot < attestation.data.slot + spec.slots_per_epoch,
Invalid::IncludedTooLate(state.slot, attestation.data.slot)
attestation.data.source_epoch == state.current_justified_epoch,
Invalid::WrongJustifiedEpoch {
state: state.current_justified_epoch,
attestation: attestation.data.source_epoch,
is_current: true,
}
);
// 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!(
attestation.data.justified_epoch == state.justified_epoch,
Invalid::WrongJustifiedEpoch(
attestation.data.justified_epoch,
state.justified_epoch,
false
)
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,35 +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,
) -> Result<Option<u64>, Error> {
let deposit_input = &deposit.deposit_data.deposit_input;
let validator_index = state
.get_validator_index(&deposit_input.pubkey)?
.and_then(|i| Some(i));
let validator_index = state.get_validator_index(&deposit_input.pubkey)?;
match validator_index {
None => Ok(None),
@ -94,12 +88,12 @@ pub fn get_existing_validator_index(
/// 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,
@ -108,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,
@ -119,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,15 +1,24 @@
use apply_rewards::apply_rewards;
use errors::EpochProcessingError as Error;
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 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 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;
@ -23,35 +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> {
// 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 mut statuses = initialize_validator_statuses(&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)?;
process_eth1_data(state, spec);
// Justification.
update_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
process_justification(state, &statuses.total_balances, spec);
// Crosslinks
// Crosslinks.
let winning_root_for_shards = process_crosslinks(state, spec)?;
// Rewards and Penalities
process_rewards_and_penalities(state, &mut statuses, &winning_root_for_shards, spec)?;
// Eth1 data.
maybe_reset_eth1_period(state, spec);
// Ejections
state.process_ejections(spec);
// Rewards and Penalities.
apply_rewards(
state,
&mut validator_statuses,
&winning_root_for_shards,
spec,
)?;
// Validator Registry
process_validator_registry(state, spec)?;
// Ejections.
process_ejections(state, spec)?;
// Final updates
update_active_tree_index_roots(state, spec)?;
update_latest_slashed_balances(state, spec);
clean_attestations(state, spec);
// Validator Registry.
update_registry_and_shuffling_data(
state,
validator_statuses.total_balances.current_epoch,
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();
@ -59,43 +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 initialize_validator_statuses(
state: &BeaconState,
spec: &ChainSpec,
) -> Result<ValidatorStatuses, BeaconStateError> {
let mut statuses = ValidatorStatuses::new(state, spec);
statuses.process_attestations(&state, &state.latest_attestations, spec)?;
Ok(statuses)
}
/// 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();
}
}
@ -110,79 +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,
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 * total_balances.previous_epoch_boundary_attesters) >= (2 * total_balances.previous_epoch)
// If the previous epoch gets justified, full the second last bit.
if (total_balances.previous_epoch_boundary_attesters * 3) >= (total_balances.previous_epoch * 2)
{
state.justification_bitfield |= 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 * total_balances.current_epoch_boundary_attesters) >= (2 * total_balances.current_epoch) {
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`:
@ -191,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
@ -221,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,
}
}
@ -250,248 +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,
statuses: &mut ValidatorStatuses,
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);
statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
// This is a hack to allow us to update index roots and slashed balances for the next epoch.
//
// The indentation here is to make it obvious where the weird stuff happens.
{
state.slot += 1;
let total_balances = &statuses.total_balances;
let base_reward_quotient =
total_balances.previous_epoch.integer_sqrt() / spec.base_reward_quotient;
// Guard against a divide-by-zero during the validator balance update.
if base_reward_quotient == 0 {
return Err(Error::BaseRewardQuotientIsZero);
}
// Guard against a divide-by-zero during the validator balance update.
if total_balances.previous_epoch == 0 {
return Err(Error::PreviousTotalBalanceIsZero);
}
// Guard against an out-of-bounds during the validator balance update.
if statuses.statuses.len() != state.validator_balances.len() {
return Err(Error::ValidatorStatusesInconsistent);
}
// Justification and finalization
let epochs_since_finality = next_epoch - state.finalized_epoch;
state.validator_balances = state
.validator_balances
.par_iter()
.enumerate()
.map(|(index, &balance)| {
let mut balance = balance;
let status = &statuses.statuses[index];
let base_reward = state.base_reward(index, base_reward_quotient, spec);
if epochs_since_finality <= 4 {
// Expected FFG source
if status.is_previous_epoch_attester {
safe_add_assign!(
balance,
base_reward * total_balances.previous_epoch_attesters
/ total_balances.previous_epoch
// 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()[..],
);
} else if status.is_active_in_previous_epoch {
safe_sub_assign!(balance, base_reward);
}
state.set_active_index_root(next_epoch, active_index_root, spec)?;
// Expected FFG target
if status.is_previous_epoch_boundary_attester {
safe_add_assign!(
balance,
base_reward * total_balances.previous_epoch_boundary_attesters
/ total_balances.previous_epoch
);
} else if status.is_active_in_previous_epoch {
safe_sub_assign!(balance, base_reward);
}
// Expected beacon chain head
if status.is_previous_epoch_head_attester {
safe_add_assign!(
balance,
base_reward * total_balances.previous_epoch_head_attesters
/ total_balances.previous_epoch
);
} else if status.is_active_in_previous_epoch {
safe_sub_assign!(balance, base_reward);
};
} else {
let inactivity_penalty = state.inactivity_penalty(
index,
epochs_since_finality,
base_reward_quotient,
// Set total slashed balances
state.set_slashed_balance(
next_epoch,
state.get_slashed_balance(current_epoch, spec)?,
spec,
);
)?;
if status.is_active_in_previous_epoch {
if !status.is_previous_epoch_attester {
safe_sub_assign!(balance, inactivity_penalty);
}
if !status.is_previous_epoch_boundary_attester {
safe_sub_assign!(balance, inactivity_penalty);
}
if !status.is_previous_epoch_head_attester {
safe_sub_assign!(balance, inactivity_penalty);
// Set randao mix
state.set_randao_mix(
next_epoch,
*state.get_randao_mix(current_epoch, spec)?,
spec,
)?;
state.slot -= 1;
}
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 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()[..]));
}
// Crosslinks
if let Some(ref info) = status.winning_root_info {
safe_add_assign!(
balance,
base_reward * info.total_attesting_balance / info.total_committee_balance
);
} else {
safe_sub_assign!(balance, base_reward);
}
balance
})
.collect();
// Attestation inclusion
// Guard against an out-of-bounds during the attester inclusion balance update.
if statuses.statuses.len() != state.validator_registry.len() {
return Err(Error::ValidatorStatusesInconsistent);
}
for (index, _validator) in state.validator_registry.iter().enumerate() {
let status = &statuses.statuses[index];
if status.is_previous_epoch_attester {
let proposer_index = status.inclusion_info.proposer_index;
let inclusion_distance = status.inclusion_info.distance;
let base_reward = state.base_reward(proposer_index, base_reward_quotient, spec);
if inclusion_distance > 0 && inclusion_distance < Slot::max_value() {
safe_add_assign!(
state.validator_balances[proposer_index],
base_reward * spec.min_attestation_inclusion_delay
/ inclusion_distance.as_u64()
)
}
}
}
state.previous_epoch_attestations = state.current_epoch_attestations.clone();
state.current_epoch_attestations = vec![];
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
{
(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
};
if should_update_validator_registy {
state.update_validator_registry(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)?
}
}
state.process_slashings(spec);
state.process_exit_queue(spec);
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

@ -9,6 +9,7 @@ pub enum EpochProcessingError {
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

@ -1,3 +1,4 @@
use super::get_attestation_participants::get_attestation_participants;
use super::WinningRootHashSet;
use types::*;
@ -22,7 +23,7 @@ pub struct WinningRootInfo {
}
/// The information required to reward a block producer for including an attestation in a block.
#[derive(Clone)]
#[derive(Clone, Copy)]
pub struct InclusionInfo {
/// The earliest slot a validator had an attestation included in the previous epoch.
pub slot: Slot,
@ -58,7 +59,11 @@ impl InclusionInfo {
/// Information required to reward some validator during the current and previous epoch.
#[derive(Default, Clone)]
pub struct AttesterStatus {
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.
@ -80,14 +85,14 @@ pub struct AttesterStatus {
/// Information used to reward the block producer of this validators earliest-included
/// attestation.
pub inclusion_info: InclusionInfo,
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 AttesterStatus {
/// Accepts some `other` `AttesterStatus` and updates `self` if required.
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.
@ -96,6 +101,8 @@ impl AttesterStatus {
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);
@ -104,7 +111,13 @@ impl AttesterStatus {
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);
self.inclusion_info.update(&other.inclusion_info);
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;
}
}
}
}
@ -136,7 +149,7 @@ pub struct TotalBalances {
#[derive(Clone)]
pub struct ValidatorStatuses {
/// Information about each individual validator from the state's validator registy.
pub statuses: Vec<AttesterStatus>,
pub statuses: Vec<ValidatorStatus>,
/// Summed balances for various sets of validators.
pub total_balances: TotalBalances,
}
@ -147,52 +160,60 @@ impl ValidatorStatuses {
/// - Active validators
/// - Total balances for the current and previous epochs.
///
/// Spec v0.4.0
pub fn new(state: &BeaconState, spec: &ChainSpec) -> Self {
/// 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 = AttesterStatus::default();
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);
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);
total_balances.previous_epoch += state.get_effective_balance(i, spec)?;
}
statuses.push(status);
}
Self {
Ok(Self {
statuses,
total_balances,
}
})
}
/// Process some attestations from the given `state` updating the `statuses` and
/// `total_balances` fields.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn process_attestations(
&mut self,
state: &BeaconState,
attestations: &[PendingAttestation],
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
for a in attestations {
for a in state
.previous_epoch_attestations
.iter()
.chain(state.current_epoch_attestations.iter())
{
let attesting_indices =
state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?;
let attesting_balance = state.get_total_balance(&attesting_indices, spec);
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
let attesting_balance = state.get_total_balance(&attesting_indices, spec)?;
let mut status = AttesterStatus::default();
let mut status = ValidatorStatus::default();
// Profile this attestation, updating the total balances and generating an
// `AttesterStatus` object that applies to all participants in the attestation.
// `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;
@ -206,11 +227,16 @@ impl ValidatorStatuses {
status.is_previous_epoch_attester = true;
// The inclusion slot and distance are only required for previous epoch attesters.
status.inclusion_info = InclusionInfo {
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, spec)?,
};
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;
@ -235,7 +261,7 @@ impl ValidatorStatuses {
/// 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.4.0
/// Spec v0.5.0
pub fn process_winning_roots(
&mut self,
state: &BeaconState,
@ -248,11 +274,10 @@ impl ValidatorStatuses {
state.get_crosslink_committees_at_slot(slot, spec)?;
// Loop through each committee in the slot.
for (crosslink_committee, shard) in crosslink_committees_at_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(&shard) {
let total_committee_balance =
state.get_total_balance(&crosslink_committee, spec);
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.
@ -272,14 +297,14 @@ impl ValidatorStatuses {
/// Returns the distance between when the attestation was created and when it was included in a
/// block.
///
/// Spec v0.4.0
/// 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.4.0
/// Spec v0.5.0
fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool {
a.data.slot.epoch(spec.slots_per_epoch) == epoch
}
@ -287,7 +312,7 @@ fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool
/// 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
/// Spec v0.5.0
fn has_common_epoch_boundary_root(
a: &PendingAttestation,
state: &BeaconState,
@ -295,25 +320,21 @@ fn has_common_epoch_boundary_root(
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)?;
let state_boundary_root = *state.get_block_root(slot, spec)?;
Ok(a.data.epoch_boundary_root == state_boundary_root)
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.4.0
/// 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_or_else(|| BeaconStateError::InsufficientBlockRoots)?;
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
let total_attesting_balance: u64 =
attesting_validator_indices
.iter()
.fold(0, |acc, i| acc + state.get_effective_balance(*i, spec));
.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,40 @@
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 test_cases: Vec<TestCase>,
}
#[test]
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();
}

View File

@ -10,6 +10,7 @@ boolean-bitfield = { path = "../utils/boolean-bitfield" }
dirs = "1.0"
ethereum-types = "0.5"
hashing = { path = "../utils/hashing" }
hex = "0.3"
honey-badger-split = { path = "../utils/honey-badger-split" }
int_to_bytes = { path = "../utils/int_to_bytes" }
log = "0.4"
@ -24,6 +25,7 @@ ssz = { path = "../utils/ssz" }
ssz_derive = { path = "../utils/ssz_derive" }
swap_or_not_shuffle = { path = "../utils/swap_or_not_shuffle" }
test_random_derive = { path = "../utils/test_random_derive" }
libp2p = { git = "https://github.com/SigP/rust-libp2p", branch = "gossipsub" }
[dev-dependencies]
env_logger = "0.6.0"

View File

@ -1,15 +1,26 @@
use super::{AggregateSignature, AttestationData, Bitfield};
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// Details an attestation that can be slashable.
///
/// Spec v0.4.0
#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)]
/// Spec v0.5.0
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
SignedRoot,
)]
pub struct Attestation {
pub aggregation_bitfield: Bitfield,
pub data: AttestationData,

View File

@ -1,20 +1,21 @@
use crate::test_utils::TestRandom;
use crate::{Crosslink, Epoch, Hash256, Slot};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// The data upon which an attestation is based.
///
/// Spec v0.4.0
/// Spec v0.5.0
#[derive(
Debug,
Clone,
PartialEq,
Default,
Serialize,
Deserialize,
Hash,
Encode,
Decode,
@ -23,14 +24,19 @@ use test_random_derive::TestRandom;
SignedRoot,
)]
pub struct AttestationData {
// LMD GHOST vote
pub slot: Slot,
pub shard: u64,
pub beacon_block_root: Hash256,
pub epoch_boundary_root: Hash256,
// FFG Vote
pub source_epoch: Epoch,
pub source_root: Hash256,
pub target_root: Hash256,
// Crosslink Vote
pub shard: u64,
pub previous_crosslink: Crosslink,
pub crosslink_data_root: Hash256,
pub latest_crosslink: Crosslink,
pub justified_epoch: Epoch,
pub justified_block_root: Hash256,
}
impl Eq for AttestationData {}

View File

@ -6,7 +6,7 @@ use ssz_derive::{Decode, Encode, TreeHash};
/// Used for pairing an attestation with a proof-of-custody.
///
/// Spec v0.4.0
/// Spec v0.5.0
#[derive(Debug, Clone, PartialEq, Default, Serialize, Encode, Decode, TreeHash)]
pub struct AttestationDataAndCustodyBit {
pub data: AttestationData,

View File

@ -0,0 +1,9 @@
use crate::*;
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
pub struct AttestationDuty {
pub slot: Slot,
pub shard: Shard,
pub committee_index: usize,
}

View File

@ -1,13 +1,13 @@
use crate::{test_utils::TestRandom, SlashableAttestation};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// Two conflicting attestations.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct AttesterSlashing {
pub slashable_attestation_1: SlashableAttestation,
pub slashable_attestation_2: SlashableAttestation,

View File

@ -1,41 +1,50 @@
use crate::test_utils::TestRandom;
use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Proposal, Slot};
use crate::*;
use bls::Signature;
use rand::RngCore;
use serde_derive::Serialize;
use ssz::{SignedRoot, TreeHash};
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// A block of the `BeaconChain`.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)]
/// Spec v0.5.0
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
SignedRoot,
)]
pub struct BeaconBlock {
pub slot: Slot,
pub parent_root: Hash256,
pub previous_block_root: Hash256,
pub state_root: Hash256,
pub randao_reveal: Signature,
pub eth1_data: Eth1Data,
pub body: BeaconBlockBody,
pub signature: Signature,
}
impl BeaconBlock {
/// Produce the first block of the Beacon Chain.
/// Returns an empty block to be used during genesis.
///
/// Spec v0.4.0
pub fn genesis(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock {
/// Spec v0.5.0
pub fn empty(spec: &ChainSpec) -> BeaconBlock {
BeaconBlock {
slot: spec.genesis_slot,
parent_root: spec.zero_hash,
state_root,
previous_block_root: spec.zero_hash,
state_root: spec.zero_hash,
body: BeaconBlockBody {
randao_reveal: spec.empty_signature.clone(),
eth1_data: Eth1Data {
deposit_root: spec.zero_hash,
block_hash: spec.zero_hash,
},
body: BeaconBlockBody {
proposer_slashings: vec![],
attester_slashings: vec![],
attestations: vec![],
@ -49,20 +58,37 @@ impl BeaconBlock {
/// Returns the `hash_tree_root` of the block.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn canonical_root(&self) -> Hash256 {
Hash256::from_slice(&self.hash_tree_root()[..])
}
/// Returns an unsigned proposal for block.
/// Returns a full `BeaconBlockHeader` of this block.
///
/// Spec v0.4.0
pub fn proposal(&self, spec: &ChainSpec) -> Proposal {
Proposal {
/// Note: This method is used instead of an `Into` impl to avoid a `Clone` of an entire block
/// when you want to have the block _and_ the header.
///
/// Note: performs a full tree-hash of `self.body`.
///
/// Spec v0.5.0
pub fn block_header(&self) -> BeaconBlockHeader {
BeaconBlockHeader {
slot: self.slot,
shard: spec.beacon_chain_shard_number,
block_root: Hash256::from_slice(&self.signed_root()),
previous_block_root: self.previous_block_root,
state_root: self.state_root,
block_body_root: Hash256::from_slice(&self.body.hash_tree_root()[..]),
signature: self.signature.clone(),
}
}
/// Returns a "temporary" header, where the `state_root` is `spec.zero_hash`.
///
/// Spec v0.5.0
pub fn temporary_block_header(&self, spec: &ChainSpec) -> BeaconBlockHeader {
BeaconBlockHeader {
state_root: spec.zero_hash,
signature: spec.empty_signature.clone(),
..self.block_header()
}
}
}

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