Merge branch 'master' into paul-sync
This commit is contained in:
62 files changed
+1506
-1386
No files matched your search
@@ -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
|
||||
@@ -22,6 +22,7 @@ members = [
|
||||
"beacon_node/db",
|
||||
"beacon_node/client",
|
||||
"beacon_node/network",
|
||||
"beacon_node/eth2-libp2p",
|
||||
"beacon_node/rpc",
|
||||
"beacon_node/version",
|
||||
"beacon_node/beacon_chain",
|
||||
|
||||
@@ -82,7 +82,7 @@ where
|
||||
let state_root = genesis_state.canonical_root();
|
||||
state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?;
|
||||
|
||||
let block_root = genesis_block.into_header().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(
|
||||
@@ -189,7 +189,7 @@ where
|
||||
pub fn advance_state(&self, slot: Slot) -> Result<(), SlotProcessingError> {
|
||||
let state_slot = self.state.read().slot;
|
||||
|
||||
let latest_block_header = self.head().beacon_block.into_header();
|
||||
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(), &latest_block_header, &self.spec)?;
|
||||
@@ -246,7 +246,10 @@ where
|
||||
/// Information is read from the present `beacon_state` shuffling, so only information from the
|
||||
/// present and prior epoch is available.
|
||||
pub fn block_proposer(&self, slot: Slot) -> Result<usize, BeaconStateError> {
|
||||
trace!("BeaconChain::block_proposer: slot: {}", slot);
|
||||
self.state
|
||||
.write()
|
||||
.build_epoch_cache(RelativeEpoch::Current, &self.spec)?;
|
||||
|
||||
let index = self.state.read().get_beacon_proposer_index(
|
||||
slot,
|
||||
RelativeEpoch::Current,
|
||||
@@ -561,7 +564,7 @@ where
|
||||
pub fn process_block(&self, block: BeaconBlock) -> Result<BlockProcessingOutcome, Error> {
|
||||
debug!("Processing block with slot {}...", block.slot);
|
||||
|
||||
let block_root = block.into_header().canonical_root();
|
||||
let block_root = block.block_header().canonical_root();
|
||||
|
||||
let present_slot = self.present_slot();
|
||||
|
||||
@@ -596,7 +599,7 @@ where
|
||||
|
||||
// Transition the parent state to the present slot.
|
||||
let mut state = parent_state;
|
||||
let previous_block_header = parent_block.into_header();
|
||||
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, &previous_block_header, &self.spec) {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
@@ -662,6 +665,8 @@ where
|
||||
|
||||
let mut state = self.state.read().clone();
|
||||
|
||||
state.build_epoch_cache(RelativeEpoch::Current, &self.spec)?;
|
||||
|
||||
trace!("Finding attestations for new block...");
|
||||
|
||||
let attestations = self
|
||||
|
||||
@@ -28,6 +28,8 @@ pub enum BeaconChainError {
|
||||
pub enum BlockProductionError {
|
||||
UnableToGetBlockRootFromState,
|
||||
BlockProcessingError(BlockProcessingError),
|
||||
BeaconStateError(BeaconStateError),
|
||||
}
|
||||
|
||||
easy_from_to!(BlockProcessingError, BlockProductionError);
|
||||
easy_from_to!(BeaconStateError, BlockProductionError);
|
||||
@@ -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
|
||||
|
||||
@@ -51,11 +51,24 @@ impl BeaconChainHarness {
|
||||
let slot_clock = TestingSlotClock::new(spec.genesis_slot.as_u64());
|
||||
let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone());
|
||||
|
||||
let (genesis_state, keypairs) = state_builder.build();
|
||||
let (mut 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());
|
||||
|
||||
genesis_state
|
||||
.build_epoch_cache(RelativeEpoch::Previous, &spec)
|
||||
.unwrap();
|
||||
genesis_state
|
||||
.build_epoch_cache(RelativeEpoch::Current, &spec)
|
||||
.unwrap();
|
||||
genesis_state
|
||||
.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)
|
||||
.unwrap();
|
||||
genesis_state
|
||||
.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)
|
||||
.unwrap();
|
||||
|
||||
// Create the Beacon Chain
|
||||
let beacon_chain = Arc::new(
|
||||
BeaconChain::from_genesis(
|
||||
@@ -194,7 +207,6 @@ impl BeaconChainHarness {
|
||||
self.increment_beacon_chain_slot();
|
||||
|
||||
// Produce a new block.
|
||||
debug!("Producing block...");
|
||||
let block = self.produce_block();
|
||||
debug!("Submitting block for processing...");
|
||||
match self.beacon_chain.process_block(block) {
|
||||
|
||||
@@ -62,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
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "libp2p"
|
||||
name = "eth2-libp2p"
|
||||
version = "0.1.0"
|
||||
authors = ["Age Manning <Age@AgeManning.com>"]
|
||||
edition = "2018"
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -10,7 +10,7 @@ sloggers = "0.3.2"
|
||||
|
||||
[dependencies]
|
||||
beacon_chain = { path = "../beacon_chain" }
|
||||
libp2p = { path = "../libp2p" }
|
||||
eth2-libp2p = { path = "../eth2-libp2p" }
|
||||
version = { path = "../version" }
|
||||
types = { path = "../../eth2/types" }
|
||||
slog = "2.4.1"
|
||||
|
||||
@@ -7,7 +7,7 @@ use beacon_chain::{
|
||||
types::{BeaconState, ChainSpec},
|
||||
CheckPoint,
|
||||
};
|
||||
use libp2p::HelloMessage;
|
||||
use eth2_libp2p::HelloMessage;
|
||||
use types::{Epoch, Hash256, Slot};
|
||||
|
||||
/// The network's API to the beacon chain.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// generates error types
|
||||
use libp2p;
|
||||
use eth2_libp2p;
|
||||
|
||||
use error_chain::{
|
||||
error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed,
|
||||
@@ -8,6 +8,6 @@ use error_chain::{
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
Libp2p(libp2p::error::Error, libp2p::error::ErrorKind);
|
||||
Libp2p(eth2_libp2p::error::Error, eth2_libp2p::error::ErrorKind);
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,5 @@ pub mod message_handler;
|
||||
pub mod service;
|
||||
pub mod sync;
|
||||
|
||||
pub use libp2p::NetworkConfig;
|
||||
pub use eth2_libp2p::NetworkConfig;
|
||||
pub use service::Service;
|
||||
@@ -3,11 +3,11 @@ use crate::error;
|
||||
use crate::service::{NetworkMessage, OutgoingMessage};
|
||||
use crate::sync::SimpleSync;
|
||||
use crossbeam_channel::{unbounded as channel, Sender};
|
||||
use futures::future;
|
||||
use libp2p::{
|
||||
use eth2_libp2p::{
|
||||
rpc::{RPCRequest, RPCResponse},
|
||||
PeerId, RPCEvent,
|
||||
};
|
||||
use futures::future;
|
||||
use slog::debug;
|
||||
use slog::warn;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -3,20 +3,20 @@ 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 libp2p::RPCEvent;
|
||||
use libp2p::Service as LibP2PService;
|
||||
use libp2p::{Libp2pEvent, PeerId};
|
||||
use slog::{debug, info, o, trace};
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::TaskExecutor;
|
||||
|
||||
/// Service that handles communication between internal services and the libp2p network service.
|
||||
/// 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<()>,
|
||||
//eth2_libp2p_service: Arc<Mutex<LibP2PService>>,
|
||||
eth2_libp2p_exit: oneshot::Sender<()>,
|
||||
network_send: crossbeam_channel::Sender<NetworkMessage>,
|
||||
//message_handler: MessageHandler,
|
||||
//message_handler_send: Sender<HandlerMessage>,
|
||||
@@ -40,20 +40,20 @@ impl Service {
|
||||
message_handler_log,
|
||||
)?;
|
||||
|
||||
// launch libp2p service
|
||||
let libp2p_log = log.new(o!("Service" => "Libp2p"));
|
||||
let libp2p_service = LibP2PService::new(config.clone(), libp2p_log)?;
|
||||
// launch eth2_libp2p service
|
||||
let eth2_libp2p_log = log.new(o!("Service" => "Libp2p"));
|
||||
let eth2_libp2p_service = LibP2PService::new(config.clone(), eth2_libp2p_log)?;
|
||||
|
||||
// TODO: Spawn thread to handle libp2p messages and pass to message handler thread.
|
||||
let libp2p_exit = spawn_service(
|
||||
libp2p_service,
|
||||
// TODO: Spawn thread to handle eth2_libp2p messages and pass to message handler thread.
|
||||
let eth2_libp2p_exit = spawn_service(
|
||||
eth2_libp2p_service,
|
||||
network_recv,
|
||||
message_handler_send,
|
||||
executor,
|
||||
log,
|
||||
)?;
|
||||
let network_service = Service {
|
||||
libp2p_exit,
|
||||
eth2_libp2p_exit,
|
||||
network_send: network_send.clone(),
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ impl Service {
|
||||
}
|
||||
|
||||
fn spawn_service(
|
||||
libp2p_service: LibP2PService,
|
||||
eth2_libp2p_service: LibP2PService,
|
||||
network_recv: crossbeam_channel::Receiver<NetworkMessage>,
|
||||
message_handler_send: crossbeam_channel::Sender<HandlerMessage>,
|
||||
executor: &TaskExecutor,
|
||||
@@ -83,7 +83,7 @@ fn spawn_service(
|
||||
// spawn on the current executor
|
||||
executor.spawn(
|
||||
network_service(
|
||||
libp2p_service,
|
||||
eth2_libp2p_service,
|
||||
network_recv,
|
||||
message_handler_send,
|
||||
log.clone(),
|
||||
@@ -100,18 +100,18 @@ fn spawn_service(
|
||||
}
|
||||
|
||||
fn network_service(
|
||||
mut libp2p_service: LibP2PService,
|
||||
mut eth2_libp2p_service: LibP2PService,
|
||||
network_recv: crossbeam_channel::Receiver<NetworkMessage>,
|
||||
message_handler_send: crossbeam_channel::Sender<HandlerMessage>,
|
||||
log: slog::Logger,
|
||||
) -> impl futures::Future<Item = (), Error = libp2p::error::Error> {
|
||||
futures::future::poll_fn(move || -> Result<_, libp2p::error::Error> {
|
||||
) -> 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() {
|
||||
match eth2_libp2p_service.poll() {
|
||||
Ok(Async::Ready(Some(Libp2pEvent::RPC(peer_id, rpc_event)))) => {
|
||||
trace!(
|
||||
libp2p_service.log,
|
||||
eth2_libp2p_service.log,
|
||||
"RPC Event: RPC message received: {:?}",
|
||||
rpc_event
|
||||
);
|
||||
@@ -120,13 +120,13 @@ fn network_service(
|
||||
.map_err(|_| "failed to send rpc to handler")?;
|
||||
}
|
||||
Ok(Async::Ready(Some(Libp2pEvent::PeerDialed(peer_id)))) => {
|
||||
debug!(libp2p_service.log, "Peer Dialed: {:?}", peer_id);
|
||||
debug!(eth2_libp2p_service.log, "Peer Dialed: {:?}", peer_id);
|
||||
message_handler_send
|
||||
.send(HandlerMessage::PeerDialed(peer_id))
|
||||
.map_err(|_| "failed to send rpc to handler")?;
|
||||
}
|
||||
Ok(Async::Ready(Some(Libp2pEvent::Message(m)))) => debug!(
|
||||
libp2p_service.log,
|
||||
eth2_libp2p_service.log,
|
||||
"Network Service: Message received: {}", m
|
||||
),
|
||||
_ => break,
|
||||
@@ -143,7 +143,7 @@ fn network_service(
|
||||
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);
|
||||
eth2_libp2p_service.swarm.send_rpc(peer_id, rpc_event);
|
||||
}
|
||||
OutgoingMessage::NotifierTest => {
|
||||
debug!(log, "Received message from notifier");
|
||||
@@ -152,7 +152,9 @@ fn network_service(
|
||||
}
|
||||
Err(TryRecvError::Empty) => break,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
return Err(libp2p::error::Error::from("Network channel disconnected"));
|
||||
return Err(eth2_libp2p::error::Error::from(
|
||||
"Network channel disconnected",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +165,7 @@ fn network_service(
|
||||
/// Types of messages that the network service can receive.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NetworkMessage {
|
||||
/// Send a message to libp2p service.
|
||||
/// Send a message to eth2_libp2p service.
|
||||
//TODO: Define typing for messages across the wire
|
||||
Send(PeerId, OutgoingMessage),
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ use crate::beacon_chain::BeaconChain;
|
||||
use crate::message_handler::NetworkContext;
|
||||
use crate::service::NetworkMessage;
|
||||
use crossbeam_channel::Sender;
|
||||
use libp2p::rpc::methods::*;
|
||||
use libp2p::rpc::{RPCRequest, RPCResponse};
|
||||
use libp2p::PeerId;
|
||||
use eth2_libp2p::rpc::methods::*;
|
||||
use eth2_libp2p::rpc::{RPCRequest, RPCResponse};
|
||||
use eth2_libp2p::PeerId;
|
||||
use slog::{debug, o};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -51,6 +51,7 @@ impl PeerSyncInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum PeerStatus {
|
||||
OnDifferentChain,
|
||||
HigherFinalizedEpoch,
|
||||
@@ -143,6 +144,12 @@ impl SimpleSync {
|
||||
debug!(self.log, "Handshake successful. Peer: {:?}", peer_id);
|
||||
self.known_peers.insert(peer_id.clone(), peer);
|
||||
|
||||
debug!(
|
||||
self.log,
|
||||
"Peer hello. Status: {:?}",
|
||||
peer.status(&self.chain)
|
||||
);
|
||||
|
||||
match peer.status(&self.chain) {
|
||||
PeerStatus::OnDifferentChain => {
|
||||
debug!(self.log, "Peer is on different chain. Peer: {:?}", peer_id);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use beacon_chain::test_utils::TestingBeaconChainBuilder;
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use libp2p::rpc::methods::*;
|
||||
use libp2p::rpc::{HelloMessage, RPCMethod, RPCRequest, RPCResponse};
|
||||
use libp2p::{PeerId, RPCEvent};
|
||||
use crossbeam_channel::{unbounded, Receiver, RecvTimeoutError, Sender};
|
||||
use eth2_libp2p::rpc::methods::*;
|
||||
use eth2_libp2p::rpc::{RPCMethod, RPCRequest, RPCResponse};
|
||||
use eth2_libp2p::{PeerId, RPCEvent};
|
||||
use network::beacon_chain::BeaconChain as NetworkBeaconChain;
|
||||
use network::message_handler::{HandlerMessage, MessageHandler};
|
||||
use network::service::{NetworkMessage, OutgoingMessage};
|
||||
@@ -43,15 +43,13 @@ impl SyncNode {
|
||||
self.sender.send(message).unwrap();
|
||||
}
|
||||
|
||||
fn recv(&self) -> NetworkMessage {
|
||||
self.receiver
|
||||
.recv_timeout(Duration::from_millis(500))
|
||||
.unwrap()
|
||||
fn recv(&self) -> Result<NetworkMessage, RecvTimeoutError> {
|
||||
self.receiver.recv_timeout(Duration::from_millis(500))
|
||||
}
|
||||
|
||||
fn recv_rpc_response(&self) -> RPCResponse {
|
||||
let network_message = self.recv();
|
||||
match network_message {
|
||||
fn recv_rpc_response(&self) -> Result<RPCResponse, RecvTimeoutError> {
|
||||
let network_message = self.recv()?;
|
||||
Ok(match network_message {
|
||||
NetworkMessage::Send(
|
||||
_peer_id,
|
||||
OutgoingMessage::RPC(RPCEvent::Response {
|
||||
@@ -61,12 +59,12 @@ impl SyncNode {
|
||||
}),
|
||||
) => result,
|
||||
_ => panic!("get_rpc_response failed! got {:?}", network_message),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn recv_rpc_request(&self) -> RPCRequest {
|
||||
let network_message = self.recv();
|
||||
match network_message {
|
||||
fn recv_rpc_request(&self) -> Result<RPCRequest, RecvTimeoutError> {
|
||||
let network_message = self.recv()?;
|
||||
Ok(match network_message {
|
||||
NetworkMessage::Send(
|
||||
_peer_id,
|
||||
OutgoingMessage::RPC(RPCEvent::Request {
|
||||
@@ -76,7 +74,7 @@ impl SyncNode {
|
||||
}),
|
||||
) => body,
|
||||
_ => panic!("get_rpc_request failed! got {:?}", network_message),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +124,7 @@ impl SyncMaster {
|
||||
let message = HandlerMessage::PeerDialed(self.peer_id.clone());
|
||||
node.send(message);
|
||||
|
||||
let request = node.recv_rpc_request();
|
||||
let request = node.recv_rpc_request().expect("No hello response");
|
||||
|
||||
match request {
|
||||
RPCRequest::Hello(_hello) => {
|
||||
@@ -151,7 +149,7 @@ impl SyncMaster {
|
||||
}
|
||||
|
||||
fn assert_sent_block_root_request(node: &SyncNode, expected: BeaconBlockRootsRequest) {
|
||||
let request = node.recv_rpc_request();
|
||||
let request = node.recv_rpc_request().expect("No block root request");
|
||||
|
||||
match request {
|
||||
RPCRequest::BeaconBlockRoots(response) => {
|
||||
|
||||
@@ -10,10 +10,7 @@ use db::{
|
||||
use log::{debug, trace};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
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(
|
||||
¤t_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(
|
||||
|
||||
@@ -10,10 +10,7 @@ use log::{debug, trace};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
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(
|
||||
¤t_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(
|
||||
|
||||
@@ -8,9 +8,7 @@ use db::{
|
||||
use log::{debug, trace};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot,
|
||||
};
|
||||
use types::{BeaconBlock, ChainSpec, Hash256, Slot};
|
||||
|
||||
//TODO: Pruning and syncing
|
||||
|
||||
@@ -61,10 +59,8 @@ where
|
||||
.get_deserialized(&state_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
||||
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
¤t_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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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(())
|
||||
}
|
||||
+2
-2
@@ -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;
|
||||
@@ -34,11 +34,10 @@ pub fn get_genesis_state(
|
||||
|
||||
// Set all the active index roots to be the genesis active index root.
|
||||
let active_validator_indices = state
|
||||
.get_active_validator_indices(spec.genesis_epoch, spec)?
|
||||
.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.latest_active_index_roots =
|
||||
vec![genesis_active_index_root; spec.latest_active_index_roots_length as usize];
|
||||
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)?;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod common;
|
||||
pub mod get_genesis_state;
|
||||
pub mod per_block_processing;
|
||||
pub mod per_epoch_processing;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use self::verify_proposer_slashing::verify_proposer_slashing;
|
||||
use crate::common::slash_validator;
|
||||
use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
|
||||
use rayon::prelude::*;
|
||||
use ssz::{SignedRoot, TreeHash};
|
||||
@@ -100,12 +101,15 @@ pub fn process_block_header(
|
||||
) -> 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.as_bytes() == &state.latest_block_header.hash_tree_root()[..],
|
||||
block.previous_block_root == *state.get_block_root(state.slot - 1, spec)?,
|
||||
Invalid::ParentBlockRootMismatch
|
||||
);
|
||||
|
||||
state.latest_block_header = block.into_temporary_header(spec);
|
||||
state.latest_block_header = block.temporary_block_header(spec);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -219,7 +223,7 @@ pub fn process_proposer_slashings(
|
||||
|
||||
// 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(())
|
||||
@@ -276,7 +280,7 @@ pub fn process_attester_slashings(
|
||||
.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)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,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,
|
||||
|
||||
@@ -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
|
||||
@@ -176,17 +176,7 @@ fn validate_attestation_signature_optional(
|
||||
);
|
||||
|
||||
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)?;
|
||||
}
|
||||
|
||||
// Crosslink data root is zero (to be removed in phase 1).
|
||||
@@ -210,32 +200,29 @@ fn validate_attestation_signature_optional(
|
||||
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(|_| {
|
||||
let validator_signed = a.aggregation_bitfield.get(i).map_err(|_| {
|
||||
Error::Invalid(Invalid::BadAggregationBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: aggregation_bitfield.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: committee.len(),
|
||||
bitfield_len: aggregation_bitfield.len(),
|
||||
bitfield_len: a.aggregation_bitfield.len(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
@@ -254,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();
|
||||
@@ -283,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
|
||||
);
|
||||
|
||||
|
||||
@@ -71,9 +71,7 @@ pub fn get_existing_validator_index(
|
||||
) -> 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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
use apply_rewards::apply_rewards;
|
||||
use errors::EpochProcessingError as Error;
|
||||
use integer_sqrt::IntegerSquareRoot;
|
||||
use process_validator_registry::process_validator_registry;
|
||||
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_validator_registry;
|
||||
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;
|
||||
|
||||
@@ -26,34 +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 the previous and next epoch caches are built.
|
||||
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::Current, 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);
|
||||
// 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();
|
||||
@@ -61,39 +84,10 @@ 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.
|
||||
///
|
||||
/// Spec v0.5.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.5.0
|
||||
pub fn initialize_validator_statuses(
|
||||
state: &BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<ValidatorStatuses, BeaconStateError> {
|
||||
let mut statuses = ValidatorStatuses::new(state, spec)?;
|
||||
|
||||
statuses.process_attestations(&state, spec)?;
|
||||
|
||||
Ok(statuses)
|
||||
}
|
||||
|
||||
/// Maybe resets the eth1 period.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) {
|
||||
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;
|
||||
|
||||
@@ -114,83 +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.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.current_justified_epoch == previous_epoch - 1)
|
||||
{
|
||||
state.finalized_epoch = state.current_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.current_justified_epoch == previous_epoch)
|
||||
{
|
||||
state.finalized_epoch = state.current_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.current_justified_epoch;
|
||||
state.current_justified_epoch = new_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`:
|
||||
@@ -240,238 +219,53 @@ pub fn process_crosslinks(
|
||||
Ok(winning_root_for_shards)
|
||||
}
|
||||
|
||||
/// Updates the following fields on the BeaconState:
|
||||
///
|
||||
/// - `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> {
|
||||
let next_epoch = state.next_epoch(spec);
|
||||
|
||||
statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
|
||||
|
||||
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 = get_base_reward(state, index, total_balances.previous_epoch, spec)
|
||||
.expect(
|
||||
"Cannot fail to access a validator balance when iterating validator balances.",
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
} else if status.is_active_in_previous_epoch {
|
||||
safe_sub_assign!(balance, base_reward);
|
||||
}
|
||||
|
||||
// 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 = get_inactivity_penalty(
|
||||
state,
|
||||
index,
|
||||
epochs_since_finality.as_u64(),
|
||||
total_balances.previous_epoch,
|
||||
spec,
|
||||
)
|
||||
.expect(
|
||||
"Cannot fail to access a validator balance when iterating validator balances.",
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if state.validator_registry[index].slashed {
|
||||
let base_reward =
|
||||
get_base_reward(state, index, total_balances.previous_epoch, spec).expect(
|
||||
"Cannot fail to access a validator balance when iterating validator balances.",
|
||||
);
|
||||
safe_sub_assign!(balance, 2 * inactivity_penalty + base_reward);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 =
|
||||
get_base_reward(state, proposer_index, total_balances.previous_epoch, spec).expect(
|
||||
"Cannot fail to access a validator balance when iterating validator balances.",
|
||||
);
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the base reward for some validator.
|
||||
/// Finish up an epoch update.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub 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
|
||||
pub 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)
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
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);
|
||||
|
||||
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];
|
||||
}
|
||||
// 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;
|
||||
|
||||
/// Removes all pending attestations from the previous epoch.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn clean_attestations(state: &mut BeaconState) {
|
||||
state.previous_epoch_attestations = vec![];
|
||||
// Set active index root
|
||||
let active_index_root = Hash256::from_slice(
|
||||
&state
|
||||
.get_active_validator_indices(next_epoch + spec.activation_exit_delay)
|
||||
.hash_tree_root()[..],
|
||||
);
|
||||
state.set_active_index_root(next_epoch, active_index_root, spec)?;
|
||||
|
||||
// Set total slashed balances
|
||||
state.set_slashed_balance(
|
||||
next_epoch,
|
||||
state.get_slashed_balance(current_epoch, spec)?,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Set randao mix
|
||||
state.set_randao_mix(
|
||||
next_epoch,
|
||||
*state.get_randao_mix(current_epoch, spec)?,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
state.slot -= 1;
|
||||
}
|
||||
|
||||
if next_epoch.as_u64() % (spec.slots_per_historical_root as u64 / spec.slots_per_epoch) == 0 {
|
||||
let historical_batch: HistoricalBatch = state.historical_batch();
|
||||
state
|
||||
.historical_roots
|
||||
.push(Hash256::from_slice(&historical_batch.hash_tree_root()[..]));
|
||||
}
|
||||
|
||||
state.previous_epoch_attestations = state.current_epoch_attestations.clone();
|
||||
state.current_epoch_attestations = vec![];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use types::{beacon_state::helpers::verify_bitfield_length, *};
|
||||
use crate::common::verify_bitfield_length;
|
||||
use types::*;
|
||||
|
||||
/// Returns validator indices which participated in the attestation.
|
||||
///
|
||||
@@ -27,7 +28,7 @@ pub fn get_attestation_participants(
|
||||
let mut participants = Vec::with_capacity(committee.len());
|
||||
for (i, validator_index) in committee.iter().enumerate() {
|
||||
match bitfield.get(i) {
|
||||
Ok(bit) if bit == true => participants.push(*validator_index),
|
||||
Ok(bit) if bit => participants.push(*validator_index),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
use super::Error;
|
||||
use types::*;
|
||||
|
||||
/// 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;
|
||||
|
||||
if should_update_validator_registry(state, spec)? {
|
||||
state.update_validator_registry(spec)?;
|
||||
|
||||
state.current_shuffling_epoch = next_epoch;
|
||||
state.current_shuffling_start_shard = (state.current_shuffling_start_shard
|
||||
+ spec.get_epoch_committee_count(
|
||||
state
|
||||
.get_active_validator_indices(current_epoch, spec)?
|
||||
.len(),
|
||||
) 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(())
|
||||
}
|
||||
|
||||
/// 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_active_validator_indices(state.current_epoch(spec), spec)?
|
||||
.len();
|
||||
let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators);
|
||||
|
||||
for shard in (0..current_epoch_committee_count)
|
||||
.into_iter()
|
||||
.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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -23,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,
|
||||
@@ -59,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.
|
||||
@@ -81,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.
|
||||
@@ -97,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);
|
||||
@@ -105,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,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,
|
||||
}
|
||||
@@ -154,7 +166,12 @@ impl ValidatorStatuses {
|
||||
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;
|
||||
@@ -193,10 +210,10 @@ impl ValidatorStatuses {
|
||||
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;
|
||||
@@ -211,7 +228,7 @@ impl ValidatorStatuses {
|
||||
|
||||
// The inclusion slot and distance are only required for previous epoch attesters.
|
||||
let relative_epoch = RelativeEpoch::from_slot(state.slot, a.data.slot, spec)?;
|
||||
status.inclusion_info = InclusionInfo {
|
||||
status.inclusion_info = Some(InclusionInfo {
|
||||
slot: a.inclusion_slot,
|
||||
distance: inclusion_distance(a),
|
||||
proposer_index: state.get_beacon_proposer_index(
|
||||
@@ -219,7 +236,7 @@ impl ValidatorStatuses {
|
||||
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;
|
||||
|
||||
@@ -25,9 +25,6 @@ pub fn per_slot_processing(
|
||||
|
||||
state.slot += 1;
|
||||
|
||||
let latest_block_root = Hash256::from_slice(&state.latest_block_header.hash_tree_root()[..]);
|
||||
state.set_block_root(state.slot - 1, latest_block_root, spec)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ impl BeaconBlock {
|
||||
/// Note: performs a full tree-hash of `self.body`.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn into_header(&self) -> BeaconBlockHeader {
|
||||
pub fn block_header(&self) -> BeaconBlockHeader {
|
||||
BeaconBlockHeader {
|
||||
slot: self.slot,
|
||||
previous_block_root: self.previous_block_root,
|
||||
@@ -84,11 +84,11 @@ impl BeaconBlock {
|
||||
/// Returns a "temporary" header, where the `state_root` is `spec.zero_hash`.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn into_temporary_header(&self, spec: &ChainSpec) -> BeaconBlockHeader {
|
||||
pub fn temporary_block_header(&self, spec: &ChainSpec) -> BeaconBlockHeader {
|
||||
BeaconBlockHeader {
|
||||
state_root: spec.zero_hash,
|
||||
signature: spec.empty_signature.clone(),
|
||||
..self.into_header()
|
||||
..self.block_header()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+476
-651
File diff suppressed because it is too large.
Load diff
@@ -107,6 +107,7 @@ impl EpochCache {
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a vec of `CrosslinkCommittee` for a given slot.
|
||||
pub fn get_crosslink_committees_at_slot(
|
||||
&self,
|
||||
slot: Slot,
|
||||
@@ -116,6 +117,8 @@ impl EpochCache {
|
||||
.get_crosslink_committees_at_slot(slot, spec)
|
||||
}
|
||||
|
||||
/// Return `Some(CrosslinkCommittee)` if the given shard has a committee during the given
|
||||
/// `epoch`.
|
||||
pub fn get_crosslink_committee_for_shard(
|
||||
&self,
|
||||
shard: Shard,
|
||||
@@ -131,6 +134,10 @@ impl EpochCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of all `validator_registry` indices where the validator is active at the given
|
||||
/// `epoch`.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec<usize> {
|
||||
let mut active = Vec::with_capacity(validators.len());
|
||||
|
||||
@@ -145,13 +152,17 @@ pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> V
|
||||
active
|
||||
}
|
||||
|
||||
/// Contains all `CrosslinkCommittees` for an epoch.
|
||||
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct EpochCrosslinkCommittees {
|
||||
/// The epoch the committees are present in.
|
||||
epoch: Epoch,
|
||||
/// Each commitee for each slot of the epoch.
|
||||
pub crosslink_committees: Vec<Vec<CrosslinkCommittee>>,
|
||||
}
|
||||
|
||||
impl EpochCrosslinkCommittees {
|
||||
/// Return a new instances where all slots have zero committees.
|
||||
fn new(epoch: Epoch, spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
epoch,
|
||||
@@ -159,6 +170,7 @@ impl EpochCrosslinkCommittees {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a vec of `CrosslinkCommittee` for a given slot.
|
||||
fn get_crosslink_committees_at_slot(
|
||||
&self,
|
||||
slot: Slot,
|
||||
@@ -176,6 +188,7 @@ impl EpochCrosslinkCommittees {
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds an `EpochCrosslinkCommittees` object.
|
||||
pub struct EpochCrosslinkCommitteesBuilder {
|
||||
epoch: Epoch,
|
||||
shuffling_start_shard: Shard,
|
||||
@@ -185,6 +198,7 @@ pub struct EpochCrosslinkCommitteesBuilder {
|
||||
}
|
||||
|
||||
impl EpochCrosslinkCommitteesBuilder {
|
||||
/// Instantiates a builder that will build for the `state`'s previous epoch.
|
||||
pub fn for_previous_epoch(
|
||||
state: &BeaconState,
|
||||
active_validator_indices: Vec<usize>,
|
||||
@@ -199,6 +213,7 @@ impl EpochCrosslinkCommitteesBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiates a builder that will build for the `state`'s next epoch.
|
||||
pub fn for_current_epoch(
|
||||
state: &BeaconState,
|
||||
active_validator_indices: Vec<usize>,
|
||||
@@ -213,6 +228,10 @@ impl EpochCrosslinkCommitteesBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiates a builder that will build for the `state`'s next epoch.
|
||||
///
|
||||
/// Note: there are two possible epoch builds for the next epoch, one where there is a registry
|
||||
/// change and one where there is not.
|
||||
pub fn for_next_epoch(
|
||||
state: &BeaconState,
|
||||
active_validator_indices: Vec<usize>,
|
||||
@@ -257,6 +276,7 @@ impl EpochCrosslinkCommitteesBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
/// Consumes the builder, returning a fully-build `EpochCrosslinkCommittee`.
|
||||
pub fn build(self, spec: &ChainSpec) -> Result<EpochCrosslinkCommittees, Error> {
|
||||
// The shuffler fails on a empty list, so if there are no active validator indices, simply
|
||||
// return an empty list.
|
||||
@@ -284,7 +304,6 @@ impl EpochCrosslinkCommitteesBuilder {
|
||||
|
||||
for (i, slot) in self.epoch.slot_iter(spec.slots_per_epoch).enumerate() {
|
||||
for j in (0..committees.len())
|
||||
.into_iter()
|
||||
.skip(i * committees_per_slot)
|
||||
.take(committees_per_slot)
|
||||
{
|
||||
|
||||
@@ -7,15 +7,19 @@ use swap_or_not_shuffle::shuffle_list;
|
||||
fn do_sane_cache_test(
|
||||
state: BeaconState,
|
||||
epoch: Epoch,
|
||||
relative_epoch: RelativeEpoch,
|
||||
validator_count: usize,
|
||||
expected_seed: Hash256,
|
||||
expected_shuffling_start: u64,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let active_indices: Vec<usize> = (0..validator_count).collect();
|
||||
|
||||
assert_eq!(
|
||||
&active_indices[..],
|
||||
state.get_active_validator_indices(epoch, &spec).unwrap(),
|
||||
state
|
||||
.get_cached_active_validator_indices(relative_epoch, &spec)
|
||||
.unwrap(),
|
||||
"Validator indices mismatch"
|
||||
);
|
||||
|
||||
@@ -101,6 +105,7 @@ fn builds_sane_current_epoch_cache() {
|
||||
do_sane_cache_test(
|
||||
state.clone(),
|
||||
state.current_epoch(&spec),
|
||||
RelativeEpoch::Current,
|
||||
validator_count as usize,
|
||||
state.current_shuffling_seed,
|
||||
state.current_shuffling_start_shard,
|
||||
@@ -117,6 +122,7 @@ fn builds_sane_previous_epoch_cache() {
|
||||
do_sane_cache_test(
|
||||
state.clone(),
|
||||
state.previous_epoch(&spec),
|
||||
RelativeEpoch::Previous,
|
||||
validator_count as usize,
|
||||
state.previous_shuffling_seed,
|
||||
state.previous_shuffling_start_shard,
|
||||
@@ -134,6 +140,7 @@ fn builds_sane_next_without_update_epoch_cache() {
|
||||
do_sane_cache_test(
|
||||
state.clone(),
|
||||
state.next_epoch(&spec),
|
||||
RelativeEpoch::NextWithoutRegistryChange,
|
||||
validator_count as usize,
|
||||
state.current_shuffling_seed,
|
||||
state.current_shuffling_start_shard,
|
||||
|
||||
@@ -6,13 +6,17 @@ type ValidatorIndex = usize;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct PubkeyCache {
|
||||
/// Maintain the number of keys added to the map. It is not sufficient to just use the HashMap
|
||||
/// len, as it does not increase when duplicate keys are added. Duplicate keys are used during
|
||||
/// testing.
|
||||
len: usize,
|
||||
map: HashMap<PublicKey, ValidatorIndex>,
|
||||
}
|
||||
|
||||
impl PubkeyCache {
|
||||
/// Returns the number of validator indices already in the map.
|
||||
/// Returns the number of validator indices added to the map so far.
|
||||
pub fn len(&self) -> ValidatorIndex {
|
||||
self.map.len()
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Inserts a validator index into the map.
|
||||
@@ -20,8 +24,9 @@ impl PubkeyCache {
|
||||
/// The added index must equal the number of validators already added to the map. This ensures
|
||||
/// that an index is never skipped.
|
||||
pub fn insert(&mut self, pubkey: PublicKey, index: ValidatorIndex) -> bool {
|
||||
if index == self.map.len() {
|
||||
if index == self.len {
|
||||
self.map.insert(pubkey, index);
|
||||
self.len += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -124,7 +124,7 @@ pub struct ChainSpec {
|
||||
impl ChainSpec {
|
||||
/// Return the number of committees in one epoch.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn get_epoch_committee_count(&self, active_validator_count: usize) -> u64 {
|
||||
std::cmp::max(
|
||||
1,
|
||||
|
||||
@@ -34,7 +34,6 @@ pub mod relative_epoch;
|
||||
pub mod slot_epoch;
|
||||
pub mod slot_height;
|
||||
pub mod validator;
|
||||
pub mod validator_registry;
|
||||
|
||||
use ethereum_types::{H160, H256, U256};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -33,7 +33,7 @@ impl RelativeEpoch {
|
||||
/// Returns the `epoch` that `self` refers to, with respect to the `base` epoch.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn into_epoch(&self, base: Epoch) -> Epoch {
|
||||
pub fn into_epoch(self, base: Epoch) -> Epoch {
|
||||
match self {
|
||||
RelativeEpoch::Previous => base - 1,
|
||||
RelativeEpoch::Current => base,
|
||||
|
||||
@@ -215,7 +215,7 @@ impl TestingBeaconStateBuilder {
|
||||
- spec.min_attestation_inclusion_delay;
|
||||
let last_slot = std::cmp::min(state.slot.as_u64(), last_slot);
|
||||
|
||||
for slot in first_slot..last_slot + 1 {
|
||||
for slot in first_slot..=last_slot {
|
||||
let slot = Slot::from(slot);
|
||||
|
||||
let committees = state
|
||||
|
||||
@@ -47,7 +47,7 @@ impl TestingDepositBuilder {
|
||||
self.deposit
|
||||
.deposit_data
|
||||
.deposit_input
|
||||
.withdrawal_credentials = withdrawal_credentials.clone();
|
||||
.withdrawal_credentials = withdrawal_credentials;
|
||||
|
||||
self.deposit.deposit_data.deposit_input.proof_of_possession = self
|
||||
.deposit
|
||||
|
||||
@@ -9,7 +9,7 @@ use test_random_derive::TestRandom;
|
||||
|
||||
/// The data submitted to the deposit contract.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/// Contains logic to manipulate a `&[Validator]`.
|
||||
/// For now, we avoid defining a newtype and just have flat functions here.
|
||||
use super::validator::*;
|
||||
use crate::Epoch;
|
||||
|
||||
/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `epoch`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec<usize> {
|
||||
let mut active = Vec::with_capacity(validators.len());
|
||||
|
||||
for (index, validator) in validators.iter().enumerate() {
|
||||
if validator.is_active_at(epoch) {
|
||||
active.push(index)
|
||||
}
|
||||
}
|
||||
|
||||
active.shrink_to_fit();
|
||||
|
||||
active
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
|
||||
#[test]
|
||||
fn can_get_empty_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let validators = vec![];
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_no_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let mut validators = vec![];
|
||||
let count_validators = 10;
|
||||
for _ in 0..count_validators {
|
||||
validators.push(Validator::default())
|
||||
}
|
||||
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_all_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let count_validators = 10;
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
|
||||
let mut validators = (0..count_validators)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let mut validator = Validator::default();
|
||||
|
||||
let activation_offset = u64::random_for_test(&mut rng);
|
||||
let exit_offset = u64::random_for_test(&mut rng);
|
||||
|
||||
validator.activation_epoch = some_epoch - activation_offset;
|
||||
validator.exit_epoch = some_epoch + exit_offset;
|
||||
|
||||
validator
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// test boundary condition by ensuring that at least one validator in the list just activated
|
||||
if let Some(validator) = validators.get_mut(0) {
|
||||
validator.activation_epoch = some_epoch;
|
||||
}
|
||||
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(
|
||||
indices,
|
||||
(0..count_validators).into_iter().collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
fn set_validators_to_default_entry_exit(validators: &mut [Validator]) {
|
||||
for validator in validators.iter_mut() {
|
||||
validator.activation_epoch = Epoch::max_value();
|
||||
validator.exit_epoch = Epoch::max_value();
|
||||
}
|
||||
}
|
||||
|
||||
// sets all `validators` to be active as of some epoch prior to `epoch`. returns the activation epoch.
|
||||
fn set_validators_to_activated(validators: &mut [Validator], epoch: Epoch) -> Epoch {
|
||||
let activation_epoch = epoch - 10;
|
||||
for validator in validators.iter_mut() {
|
||||
validator.activation_epoch = activation_epoch;
|
||||
}
|
||||
activation_epoch
|
||||
}
|
||||
|
||||
// sets all `validators` to be exited as of some epoch before `epoch`.
|
||||
fn set_validators_to_exited(
|
||||
validators: &mut [Validator],
|
||||
epoch: Epoch,
|
||||
activation_epoch: Epoch,
|
||||
) {
|
||||
assert!(activation_epoch < epoch);
|
||||
let mut exit_epoch = activation_epoch + 10;
|
||||
while exit_epoch >= epoch {
|
||||
exit_epoch -= 1;
|
||||
}
|
||||
assert!(activation_epoch < exit_epoch && exit_epoch < epoch);
|
||||
|
||||
for validator in validators.iter_mut() {
|
||||
validator.exit_epoch = exit_epoch;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_some_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
const COUNT_PARTITIONS: usize = 3;
|
||||
const COUNT_VALIDATORS: usize = 3 * COUNT_PARTITIONS;
|
||||
let some_epoch: Epoch = Epoch::random_for_test(&mut rng);
|
||||
|
||||
let mut validators = (0..COUNT_VALIDATORS)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let mut validator = Validator::default();
|
||||
|
||||
let activation_offset = Epoch::random_for_test(&mut rng);
|
||||
let exit_offset = Epoch::random_for_test(&mut rng);
|
||||
|
||||
validator.activation_epoch = some_epoch - activation_offset;
|
||||
validator.exit_epoch = some_epoch + exit_offset;
|
||||
|
||||
validator
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// we partition the set into partitions based on lifecycle:
|
||||
for (i, chunk) in validators.chunks_exact_mut(COUNT_PARTITIONS).enumerate() {
|
||||
match i {
|
||||
0 => {
|
||||
// 1. not activated (Default::default())
|
||||
set_validators_to_default_entry_exit(chunk);
|
||||
}
|
||||
1 => {
|
||||
// 2. activated, but not exited
|
||||
set_validators_to_activated(chunk, some_epoch);
|
||||
// test boundary condition by ensuring that at least one validator in the list just activated
|
||||
if let Some(validator) = chunk.get_mut(0) {
|
||||
validator.activation_epoch = some_epoch;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
// 3. exited
|
||||
let activation_epoch = set_validators_to_activated(chunk, some_epoch);
|
||||
set_validators_to_exited(chunk, some_epoch, activation_epoch);
|
||||
// test boundary condition by ensuring that at least one validator in the list just exited
|
||||
if let Some(validator) = chunk.get_mut(0) {
|
||||
validator.exit_epoch = some_epoch;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(
|
||||
"constants local to this test not in sync with generation of test case"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![3, 4, 5]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user