diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..a14dd7a51 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/Cargo.toml b/Cargo.toml index d34f6fd30..cb070cc2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 01787f95b..4b151d70b 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -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 { - 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 { 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 diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 58c3f87ae..b5f17efd2 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -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); diff --git a/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml index aea7dcf31..0c4f5004b 100644 --- a/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml +++ b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml @@ -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 diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 800dd3ce6..4b5d5558f 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -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) { diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index 1361127a1..f65b45505 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -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 } diff --git a/beacon_node/beacon_chain/test_harness/src/test_case/config.rs b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs index f336b9d53..12d5da2d7 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case/config.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs @@ -20,6 +20,8 @@ pub struct Config { pub deposits_for_chain_start: usize, /// Number of slots in an epoch. pub slots_per_epoch: Option, + /// Affects the number of epochs a validator must be active before they can withdraw. + pub persistent_committee_period: Option, /// 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), diff --git a/beacon_node/libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml similarity index 96% rename from beacon_node/libp2p/Cargo.toml rename to beacon_node/eth2-libp2p/Cargo.toml index e863c4d78..808f5ab17 100644 --- a/beacon_node/libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "libp2p" +name = "eth2-libp2p" version = "0.1.0" authors = ["Age Manning "] edition = "2018" diff --git a/beacon_node/libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs similarity index 100% rename from beacon_node/libp2p/src/behaviour.rs rename to beacon_node/eth2-libp2p/src/behaviour.rs diff --git a/beacon_node/libp2p/src/error.rs b/beacon_node/eth2-libp2p/src/error.rs similarity index 100% rename from beacon_node/libp2p/src/error.rs rename to beacon_node/eth2-libp2p/src/error.rs diff --git a/beacon_node/libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs similarity index 100% rename from beacon_node/libp2p/src/lib.rs rename to beacon_node/eth2-libp2p/src/lib.rs diff --git a/beacon_node/libp2p/src/network_config.rs b/beacon_node/eth2-libp2p/src/network_config.rs similarity index 100% rename from beacon_node/libp2p/src/network_config.rs rename to beacon_node/eth2-libp2p/src/network_config.rs diff --git a/beacon_node/libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs similarity index 100% rename from beacon_node/libp2p/src/rpc/methods.rs rename to beacon_node/eth2-libp2p/src/rpc/methods.rs diff --git a/beacon_node/libp2p/src/rpc/mod.rs b/beacon_node/eth2-libp2p/src/rpc/mod.rs similarity index 100% rename from beacon_node/libp2p/src/rpc/mod.rs rename to beacon_node/eth2-libp2p/src/rpc/mod.rs diff --git a/beacon_node/libp2p/src/rpc/protocol.rs b/beacon_node/eth2-libp2p/src/rpc/protocol.rs similarity index 100% rename from beacon_node/libp2p/src/rpc/protocol.rs rename to beacon_node/eth2-libp2p/src/rpc/protocol.rs diff --git a/beacon_node/libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs similarity index 100% rename from beacon_node/libp2p/src/service.rs rename to beacon_node/eth2-libp2p/src/service.rs diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index 260ee0896..a53097159 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -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" diff --git a/beacon_node/network/src/beacon_chain.rs b/beacon_node/network/src/beacon_chain.rs index 63e1eb6dd..5246c87c2 100644 --- a/beacon_node/network/src/beacon_chain.rs +++ b/beacon_node/network/src/beacon_chain.rs @@ -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. diff --git a/beacon_node/network/src/error.rs b/beacon_node/network/src/error.rs index 2005f76ae..cdd6b6209 100644 --- a/beacon_node/network/src/error.rs +++ b/beacon_node/network/src/error.rs @@ -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); } } diff --git a/beacon_node/network/src/lib.rs b/beacon_node/network/src/lib.rs index 822b05509..87f8368a5 100644 --- a/beacon_node/network/src/lib.rs +++ b/beacon_node/network/src/lib.rs @@ -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; diff --git a/beacon_node/network/src/message_handler.rs b/beacon_node/network/src/message_handler.rs index afd407abe..5b39de997 100644 --- a/beacon_node/network/src/message_handler.rs +++ b/beacon_node/network/src/message_handler.rs @@ -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; diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index c3045d280..14f994e4a 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -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>, - libp2p_exit: oneshot::Sender<()>, + //eth2_libp2p_service: Arc>, + eth2_libp2p_exit: oneshot::Sender<()>, network_send: crossbeam_channel::Sender, //message_handler: MessageHandler, //message_handler_send: Sender, @@ -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, message_handler_send: crossbeam_channel::Sender, 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, message_handler_send: crossbeam_channel::Sender, log: slog::Logger, -) -> impl futures::Future { - futures::future::poll_fn(move || -> Result<_, libp2p::error::Error> { +) -> impl futures::Future { + 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), } diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 0d75f3739..6a40ecf60 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -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); diff --git a/beacon_node/network/tests/tests.rs b/beacon_node/network/tests/tests.rs index b5635cf8c..7941ffb99 100644 --- a/beacon_node/network/tests/tests.rs +++ b/beacon_node/network/tests/tests.rs @@ -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 { + 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 { + 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 { + 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) => { diff --git a/eth2/fork_choice/src/bitwise_lmd_ghost.rs b/eth2/fork_choice/src/bitwise_lmd_ghost.rs index 9410fd203..8ae0251d2 100644 --- a/eth2/fork_choice/src/bitwise_lmd_ghost.rs +++ b/eth2/fork_choice/src/bitwise_lmd_ghost.rs @@ -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( diff --git a/eth2/fork_choice/src/optimized_lmd_ghost.rs b/eth2/fork_choice/src/optimized_lmd_ghost.rs index e1b8914a6..ee2919e85 100644 --- a/eth2/fork_choice/src/optimized_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimized_lmd_ghost.rs @@ -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( diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs index 0788ac171..4b236cba4 100644 --- a/eth2/fork_choice/src/slow_lmd_ghost.rs +++ b/eth2/fork_choice/src/slow_lmd_ghost.rs @@ -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( diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index 80fbbbe20..3ce63eeb7 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -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(); diff --git a/eth2/state_processing/src/common/exit_validator.rs b/eth2/state_processing/src/common/exit_validator.rs new file mode 100644 index 000000000..8ab530b18 --- /dev/null +++ b/eth2/state_processing/src/common/exit_validator.rs @@ -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(()) +} diff --git a/eth2/state_processing/src/common/mod.rs b/eth2/state_processing/src/common/mod.rs new file mode 100644 index 000000000..49898d10f --- /dev/null +++ b/eth2/state_processing/src/common/mod.rs @@ -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; diff --git a/eth2/state_processing/src/common/slash_validator.rs b/eth2/state_processing/src/common/slash_validator.rs new file mode 100644 index 000000000..9be87b978 --- /dev/null +++ b/eth2/state_processing/src/common/slash_validator.rs @@ -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(()) +} diff --git a/eth2/types/src/beacon_state/helpers.rs b/eth2/state_processing/src/common/verify_bitfield.rs similarity index 93% rename from eth2/types/src/beacon_state/helpers.rs rename to eth2/state_processing/src/common/verify_bitfield.rs index adae7bab4..03fcdbb67 100644 --- a/eth2/types/src/beacon_state/helpers.rs +++ b/eth2/state_processing/src/common/verify_bitfield.rs @@ -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; diff --git a/eth2/state_processing/src/get_genesis_state.rs b/eth2/state_processing/src/get_genesis_state.rs index 3c6612349..7c4d4cafd 100644 --- a/eth2/state_processing/src/get_genesis_state.rs +++ b/eth2/state_processing/src/get_genesis_state.rs @@ -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)?; diff --git a/eth2/state_processing/src/lib.rs b/eth2/state_processing/src/lib.rs index 78dc7270d..6757b5dbd 100644 --- a/eth2/state_processing/src/lib.rs +++ b/eth2/state_processing/src/lib.rs @@ -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; diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 78cf927f5..dc83abb3f 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -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, diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 272eeb18b..2143988a4 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -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 ); diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs index 80d8bc24f..a3a0f5734 100644 --- a/eth2/state_processing/src/per_block_processing/verify_deposit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -71,9 +71,7 @@ pub fn get_existing_validator_index( ) -> Result, 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), diff --git a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs index aa9a32196..d3ab5e398 100644 --- a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs @@ -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 diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index d1bb4269a..fcdc668f4 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -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; /// 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 { - 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 { - 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 { - 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 { - 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(()) } diff --git a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs new file mode 100644 index 000000000..ce5fccb21 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs @@ -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, + 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, + 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, + 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 { + 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 { + 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 +} diff --git a/eth2/state_processing/src/per_epoch_processing/errors.rs b/eth2/state_processing/src/per_epoch_processing/errors.rs index 94fc0cca5..4632e83bb 100644 --- a/eth2/state_processing/src/per_epoch_processing/errors.rs +++ b/eth2/state_processing/src/per_epoch_processing/errors.rs @@ -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. /// diff --git a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs index d822e434d..52ba0274b 100644 --- a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs +++ b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs @@ -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), _ => {} } } diff --git a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs new file mode 100644 index 000000000..a60d92187 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs @@ -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 = 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(()) +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs new file mode 100644 index 000000000..074db1d08 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs @@ -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 = (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; +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs new file mode 100644 index 000000000..88777472c --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs @@ -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(()) +} diff --git a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs deleted file mode 100644 index 26ebd60b3..000000000 --- a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs +++ /dev/null @@ -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 { - 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) -} diff --git a/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs b/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs new file mode 100644 index 000000000..0b18c2571 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs @@ -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 { + 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) + } +} diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index bcbca8244..50f3ec372 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -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, /// Information used to reward/penalize the validator if they voted in the super-majority for /// some shard block. pub winning_root_info: Option, } -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, + pub statuses: Vec, /// 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; diff --git a/eth2/state_processing/src/per_slot_processing.rs b/eth2/state_processing/src/per_slot_processing.rs index a90c5b408..8f02b70e3 100644 --- a/eth2/state_processing/src/per_slot_processing.rs +++ b/eth2/state_processing/src/per_slot_processing.rs @@ -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(()) } diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index b966751ed..6a3f1a354 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -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() } } } diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 1b2424774..1e5278124 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,4 +1,4 @@ -use self::epoch_cache::{EpochCache, Error as EpochCacheError}; +use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError}; use crate::test_utils::TestRandom; use crate::*; use int_to_bytes::int_to_bytes32; @@ -10,7 +10,6 @@ use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; mod epoch_cache; -pub mod helpers; mod pubkey_cache; mod tests; @@ -19,17 +18,13 @@ pub const CACHED_EPOCHS: usize = 4; #[derive(Debug, PartialEq)] pub enum Error { EpochOutOfBounds, - /// The supplied shard is unknown. It may be larger than the maximum shard count, or not in a - /// committee for the given slot. SlotOutOfBounds, ShardOutOfBounds, - UnableToShuffle, UnknownValidator, + UnableToDetermineProducer, InvalidBitfield, ValidatorIsWithdrawable, InsufficientRandaoMixes, - NoValidators, - UnableToDetermineProducer, InsufficientBlockRoots, InsufficientIndexRoots, InsufficientAttestations, @@ -37,27 +32,16 @@ pub enum Error { InsufficientSlashedBalances, InsufficientStateRoots, NoCommitteeForShard, - EpochCacheUninitialized(RelativeEpoch), PubkeyCacheInconsistent, PubkeyCacheIncomplete { cache_len: usize, registry_len: usize, }, + EpochCacheUninitialized(RelativeEpoch), RelativeEpochError(RelativeEpochError), EpochCacheError(EpochCacheError), } -macro_rules! safe_add_assign { - ($a: expr, $b: expr) => { - $a = $a.saturating_add($b); - }; -} -macro_rules! safe_sub_assign { - ($a: expr, $b: expr) => { - $a = $a.saturating_sub($b); - }; -} - /// The state of the `BeaconChain` at some slot. /// /// Spec v0.5.0 @@ -95,10 +79,10 @@ pub struct BeaconState { // Recent state pub latest_crosslinks: Vec, - pub latest_block_roots: Vec, - pub latest_state_roots: Vec, - pub latest_active_index_roots: Vec, - pub latest_slashed_balances: Vec, + latest_block_roots: Vec, + latest_state_roots: Vec, + latest_active_index_roots: Vec, + latest_slashed_balances: Vec, pub latest_block_header: BeaconBlockHeader, pub historical_roots: Vec, @@ -178,7 +162,7 @@ impl BeaconState { latest_state_roots: vec![spec.zero_hash; spec.slots_per_historical_root], latest_active_index_roots: vec![spec.zero_hash; spec.latest_active_index_roots_length], latest_slashed_balances: vec![0; spec.latest_slashed_exit_length], - latest_block_header: BeaconBlock::empty(spec).into_temporary_header(spec), + latest_block_header: BeaconBlock::empty(spec).temporary_block_header(spec), historical_roots: vec![], /* @@ -209,6 +193,474 @@ impl BeaconState { Hash256::from_slice(&self.hash_tree_root()[..]) } + pub fn historical_batch(&self) -> HistoricalBatch { + HistoricalBatch { + block_roots: self.latest_block_roots.clone(), + state_roots: self.latest_state_roots.clone(), + } + } + + /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise + /// returns `None`. + /// + /// Requires a fully up-to-date `pubkey_cache`, returns an error if this is not the case. + pub fn get_validator_index(&self, pubkey: &PublicKey) -> Result, Error> { + if self.pubkey_cache.len() == self.validator_registry.len() { + Ok(self.pubkey_cache.get(pubkey)) + } else { + Err(Error::PubkeyCacheIncomplete { + cache_len: self.pubkey_cache.len(), + registry_len: self.validator_registry.len(), + }) + } + } + + /// The epoch corresponding to `self.slot`. + /// + /// Spec v0.5.0 + pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { + self.slot.epoch(spec.slots_per_epoch) + } + + /// The epoch prior to `self.current_epoch()`. + /// + /// If the current epoch is the genesis epoch, the genesis_epoch is returned. + /// + /// Spec v0.5.0 + pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { + self.current_epoch(&spec) - 1 + } + + /// The epoch following `self.current_epoch()`. + /// + /// Spec v0.5.0 + pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { + self.current_epoch(spec) + 1 + } + + /// Returns the active validator indices for the given epoch, assuming there is no validator + /// registry update in the next epoch. + /// + /// This uses the cache, so it saves an iteration over the validator registry, however it can + /// not return a result for any epoch before the previous epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_cached_active_validator_indices( + &self, + relative_epoch: RelativeEpoch, + spec: &ChainSpec, + ) -> Result<&[usize], Error> { + let cache = self.cache(relative_epoch, spec)?; + + Ok(&cache.active_validator_indices) + } + + /// Returns the active validator indices for the given epoch. + /// + /// Does not utilize the cache, performs a full iteration over the validator registry. + /// + /// Spec v0.5.0 + pub fn get_active_validator_indices(&self, epoch: Epoch) -> Vec { + get_active_validator_indices(&self.validator_registry, epoch) + } + + /// Returns the crosslink committees for some slot. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_crosslink_committees_at_slot( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Result<&Vec, Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = match RelativeEpoch::from_slot(self.slot, slot, spec) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?) + } + + /// Returns the crosslink committees for some shard in an epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_crosslink_committee_for_shard( + &self, + epoch: Epoch, + shard: Shard, + spec: &ChainSpec, + ) -> Result<&CrosslinkCommittee, Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = match RelativeEpoch::from_epoch(self.current_epoch(spec), epoch) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(cache + .get_crosslink_committee_for_shard(shard, spec) + .ok_or_else(|| Error::NoCommitteeForShard)?) + } + + /// Returns the beacon proposer index for the `slot`. + /// + /// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned. + /// + /// Spec v0.5.0 + pub fn get_beacon_proposer_index( + &self, + slot: Slot, + relative_epoch: RelativeEpoch, + spec: &ChainSpec, + ) -> Result { + let cache = self.cache(relative_epoch, spec)?; + + let committees = cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?; + + let epoch = slot.epoch(spec.slots_per_epoch); + + committees + .first() + .ok_or(Error::UnableToDetermineProducer) + .and_then(|first| { + let index = epoch + .as_usize() + .checked_rem(first.committee.len()) + .ok_or(Error::UnableToDetermineProducer)?; + Ok(first.committee[index]) + }) + } + + /// Safely obtains the index for latest block roots, given some `slot`. + /// + /// Spec v0.5.0 + fn get_latest_block_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { + if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { + let i = slot.as_usize() % spec.slots_per_historical_root; + if i >= self.latest_block_roots.len() { + Err(Error::InsufficientStateRoots) + } else { + Ok(i) + } + } else { + Err(BeaconStateError::SlotOutOfBounds) + } + } + + /// Return the block root at a recent `slot`. + /// + /// Spec v0.5.0 + pub fn get_block_root( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Result<&Hash256, BeaconStateError> { + let i = self.get_latest_block_roots_index(slot, spec)?; + Ok(&self.latest_block_roots[i]) + } + + /// Sets the block root for some given slot. + /// + /// Spec v0.5.0 + pub fn set_block_root( + &mut self, + slot: Slot, + block_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), BeaconStateError> { + let i = self.get_latest_block_roots_index(slot, spec)?; + self.latest_block_roots[i] = block_root; + Ok(()) + } + + /// Safely obtains the index for `latest_randao_mixes` + /// + /// Spec v0.5.0 + fn get_randao_mix_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let current_epoch = self.current_epoch(spec); + + if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) + & (epoch <= current_epoch) + { + let i = epoch.as_usize() % spec.latest_randao_mixes_length; + if i < self.latest_randao_mixes.len() { + Ok(i) + } else { + Err(Error::InsufficientRandaoMixes) + } + } else { + Err(Error::EpochOutOfBounds) + } + } + + /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. + /// + /// # Errors: + /// + /// See `Self::get_randao_mix`. + /// + /// Spec v0.5.0 + pub fn update_randao_mix( + &mut self, + epoch: Epoch, + signature: &Signature, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = epoch.as_usize() % spec.latest_randao_mixes_length; + + let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature))); + + self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash; + + Ok(()) + } + + /// Return the randao mix at a recent ``epoch``. + /// + /// Spec v0.5.0 + pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> { + let i = self.get_randao_mix_index(epoch, spec)?; + Ok(&self.latest_randao_mixes[i]) + } + + /// Set the randao mix at a recent ``epoch``. + /// + /// Spec v0.5.0 + pub fn set_randao_mix( + &mut self, + epoch: Epoch, + mix: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_randao_mix_index(epoch, spec)?; + self.latest_randao_mixes[i] = mix; + Ok(()) + } + + /// Safely obtains the index for `latest_active_index_roots`, given some `epoch`. + /// + /// Spec v0.5.0 + fn get_active_index_root_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let current_epoch = self.current_epoch(spec); + + if (current_epoch - spec.latest_active_index_roots_length as u64 + + spec.activation_exit_delay + < epoch) + & (epoch <= current_epoch + spec.activation_exit_delay) + { + let i = epoch.as_usize() % spec.latest_active_index_roots_length; + if i < self.latest_active_index_roots.len() { + Ok(i) + } else { + Err(Error::InsufficientIndexRoots) + } + } else { + Err(Error::EpochOutOfBounds) + } + } + + /// Return the `active_index_root` at a recent `epoch`. + /// + /// Spec v0.5.0 + pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = self.get_active_index_root_index(epoch, spec)?; + Ok(self.latest_active_index_roots[i]) + } + + /// Set the `active_index_root` at a recent `epoch`. + /// + /// Spec v0.5.0 + pub fn set_active_index_root( + &mut self, + epoch: Epoch, + index_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_active_index_root_index(epoch, spec)?; + self.latest_active_index_roots[i] = index_root; + Ok(()) + } + + /// Replace `active_index_roots` with clones of `index_root`. + /// + /// Spec v0.5.0 + pub fn fill_active_index_roots_with(&mut self, index_root: Hash256, spec: &ChainSpec) { + self.latest_active_index_roots = + vec![index_root; spec.latest_active_index_roots_length as usize] + } + + /// Safely obtains the index for latest state roots, given some `slot`. + /// + /// Spec v0.5.0 + fn get_latest_state_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { + if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { + let i = slot.as_usize() % spec.slots_per_historical_root; + if i >= self.latest_state_roots.len() { + Err(Error::InsufficientStateRoots) + } else { + Ok(i) + } + } else { + Err(BeaconStateError::SlotOutOfBounds) + } + } + + /// Gets the state root for some slot. + /// + /// Spec v0.5.0 + pub fn get_state_root(&mut self, slot: Slot, spec: &ChainSpec) -> Result<&Hash256, Error> { + let i = self.get_latest_state_roots_index(slot, spec)?; + Ok(&self.latest_state_roots[i]) + } + + /// Sets the latest state root for slot. + /// + /// Spec v0.5.0 + pub fn set_state_root( + &mut self, + slot: Slot, + state_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_latest_state_roots_index(slot, spec)?; + self.latest_state_roots[i] = state_root; + Ok(()) + } + + /// Safely obtains the index for `latest_slashed_balances`, given some `epoch`. + /// + /// Spec v0.5.0 + fn get_slashed_balance_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = epoch.as_usize() % spec.latest_slashed_exit_length; + + // NOTE: the validity of the epoch is not checked. It is not in the spec but it's probably + // useful to have. + if i < self.latest_slashed_balances.len() { + Ok(i) + } else { + Err(Error::InsufficientSlashedBalances) + } + } + + /// Gets the total slashed balances for some epoch. + /// + /// Spec v0.5.0 + pub fn get_slashed_balance(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let i = self.get_slashed_balance_index(epoch, spec)?; + Ok(self.latest_slashed_balances[i]) + } + + /// Sets the total slashed balances for some epoch. + /// + /// Spec v0.5.0 + pub fn set_slashed_balance( + &mut self, + epoch: Epoch, + balance: u64, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_slashed_balance_index(epoch, spec)?; + self.latest_slashed_balances[i] = balance; + Ok(()) + } + + /// Generate a seed for the given `epoch`. + /// + /// Spec v0.5.0 + pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + let mut input = self + .get_randao_mix(epoch - spec.min_seed_lookahead, spec)? + .as_bytes() + .to_vec(); + + input.append(&mut self.get_active_index_root(epoch, spec)?.as_bytes().to_vec()); + + input.append(&mut int_to_bytes32(epoch.as_u64())); + + Ok(Hash256::from_slice(&hash(&input[..])[..])) + } + + /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. + /// + /// Spec v0.5.0 + pub fn get_effective_balance( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result { + let balance = self + .validator_balances + .get(validator_index) + .ok_or_else(|| Error::UnknownValidator)?; + Ok(std::cmp::min(*balance, spec.max_deposit_amount)) + } + + /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. + /// + /// Spec v0.5.0 + pub fn get_delayed_activation_exit_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Epoch { + epoch + 1 + spec.activation_exit_delay + } + + /// Initiate an exit for the validator of the given `index`. + /// + /// Spec v0.5.0 + pub fn initiate_validator_exit(&mut self, validator_index: usize) { + self.validator_registry[validator_index].initiated_exit = true; + } + + /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an + /// attestation. + /// + /// Only reads the current epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_attestation_duties( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result<&Option, Error> { + let cache = self.cache(RelativeEpoch::Current, spec)?; + + Ok(cache + .attestation_duties + .get(validator_index) + .ok_or_else(|| Error::UnknownValidator)?) + } + + /// Return the combined effective balance of an array of validators. + /// + /// Spec v0.5.0 + pub fn get_total_balance( + &self, + validator_indices: &[usize], + spec: &ChainSpec, + ) -> Result { + validator_indices.iter().try_fold(0_u64, |acc, i| { + self.get_effective_balance(*i, spec) + .and_then(|bal| Ok(bal + acc)) + }) + } + /// Build an epoch cache, unless it is has already been built. pub fn build_epoch_cache( &mut self, @@ -311,633 +763,6 @@ impl BeaconState { pub fn drop_pubkey_cache(&mut self) { self.pubkey_cache = PubkeyCache::default() } - - /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise - /// returns `None`. - /// - /// Requires a fully up-to-date `pubkey_cache`, returns an error if this is not the case. - pub fn get_validator_index(&self, pubkey: &PublicKey) -> Result, Error> { - if self.pubkey_cache.len() == self.validator_registry.len() { - Ok(self.pubkey_cache.get(pubkey)) - } else { - Err(Error::PubkeyCacheIncomplete { - cache_len: self.pubkey_cache.len(), - registry_len: self.validator_registry.len(), - }) - } - } - - /// The epoch corresponding to `self.slot`. - /// - /// Spec v0.5.0 - pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { - self.slot.epoch(spec.slots_per_epoch) - } - - /// The epoch prior to `self.current_epoch()`. - /// - /// If the current epoch is the genesis epoch, the genesis_epoch is returned. - /// - /// Spec v0.5.0 - pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(&spec) - 1 - } - - /// The epoch following `self.current_epoch()`. - /// - /// Spec v0.5.0 - pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(spec) + 1 - } - - /// Returns the active validator indices for the given epoch, assuming there is no validator - /// registry update in the next epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.5.0 - pub fn get_active_validator_indices( - &self, - epoch: Epoch, - spec: &ChainSpec, - ) -> Result<&[usize], Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = - match RelativeEpoch::from_epoch(self.slot.epoch(spec.slots_per_epoch), epoch) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(&cache.active_validator_indices) - } - - /// Returns the crosslink committees for some slot. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.5.0 - pub fn get_crosslink_committees_at_slot( - &self, - slot: Slot, - spec: &ChainSpec, - ) -> Result<&Vec, Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = match RelativeEpoch::from_slot(self.slot, slot, spec) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(cache - .get_crosslink_committees_at_slot(slot, spec) - .ok_or_else(|| Error::SlotOutOfBounds)?) - } - - /// Returns the crosslink committees for some shard in an epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn get_crosslink_committee_for_shard( - &self, - epoch: Epoch, - shard: Shard, - spec: &ChainSpec, - ) -> Result<&CrosslinkCommittee, Error> { - // If the slot is in the next epoch, assume there was no validator registry update. - let relative_epoch = match RelativeEpoch::from_epoch(self.current_epoch(spec), epoch) { - Err(RelativeEpochError::AmbiguiousNextEpoch) => { - Ok(RelativeEpoch::NextWithoutRegistryChange) - } - e => e, - }?; - - let cache = self.cache(relative_epoch, spec)?; - - Ok(cache - .get_crosslink_committee_for_shard(shard, spec) - .ok_or_else(|| Error::NoCommitteeForShard)?) - } - - /// Safely obtains the index for latest block roots, given some `slot`. - /// - /// Spec v0.5.0 - fn get_latest_block_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { - if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { - let i = slot.as_usize() % spec.slots_per_historical_root; - if i >= self.latest_block_roots.len() { - Err(Error::InsufficientStateRoots) - } else { - Ok(i) - } - } else { - Err(BeaconStateError::SlotOutOfBounds) - } - } - - /// Return the block root at a recent `slot`. - /// - /// Spec v0.5.0 - pub fn get_block_root( - &self, - slot: Slot, - spec: &ChainSpec, - ) -> Result<&Hash256, BeaconStateError> { - let i = self.get_latest_block_roots_index(slot, spec)?; - Ok(&self.latest_block_roots[i]) - } - - /// Sets the block root for some given slot. - /// - /// Spec v0.5.0 - pub fn set_block_root( - &mut self, - slot: Slot, - block_root: Hash256, - spec: &ChainSpec, - ) -> Result<(), BeaconStateError> { - let i = self.get_latest_block_roots_index(slot, spec)?; - Ok(self.latest_block_roots[i] = block_root) - } - - /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. - /// - /// # Errors: - /// - /// See `Self::get_randao_mix`. - /// - /// Spec v0.5.0 - pub fn update_randao_mix( - &mut self, - epoch: Epoch, - signature: &Signature, - spec: &ChainSpec, - ) -> Result<(), Error> { - let i = epoch.as_usize() % spec.latest_randao_mixes_length; - - let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature))); - - self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash; - - Ok(()) - } - - /// Return the randao mix at a recent ``epoch``. - /// - /// # Errors: - /// - `InsufficientRandaoMixes` if `self.latest_randao_mixes` is shorter than - /// `spec.latest_randao_mixes_length`. - /// - `EpochOutOfBounds` if the state no longer stores randao mixes for the given `epoch`. - /// - /// Spec v0.5.0 - pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> { - let current_epoch = self.current_epoch(spec); - - if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) - & (epoch <= current_epoch) - { - self.latest_randao_mixes - .get(epoch.as_usize() % spec.latest_randao_mixes_length) - .ok_or_else(|| Error::InsufficientRandaoMixes) - } else { - Err(Error::EpochOutOfBounds) - } - } - - /// Return the index root at a recent `epoch`. - /// - /// Spec v0.4.0 - pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Option { - let current_epoch = self.current_epoch(spec); - - if (current_epoch - spec.latest_active_index_roots_length as u64 - + spec.activation_exit_delay - < epoch) - & (epoch <= current_epoch + spec.activation_exit_delay) - { - Some( - self.latest_active_index_roots - [epoch.as_usize() % spec.latest_active_index_roots_length], - ) - } else { - None - } - } - - /// Safely obtains the index for latest state roots, given some `slot`. - /// - /// Spec v0.5.0 - fn get_latest_state_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { - if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { - let i = slot.as_usize() % spec.slots_per_historical_root; - if i >= self.latest_state_roots.len() { - Err(Error::InsufficientStateRoots) - } else { - Ok(i) - } - } else { - Err(BeaconStateError::SlotOutOfBounds) - } - } - - /// Gets the state root for some slot. - /// - /// Spec v0.5.0 - pub fn get_state_root(&mut self, slot: Slot, spec: &ChainSpec) -> Result<&Hash256, Error> { - let i = self.get_latest_state_roots_index(slot, spec)?; - Ok(&self.latest_state_roots[i]) - } - - /// Sets the latest state root for slot. - /// - /// Spec v0.5.0 - pub fn set_state_root( - &mut self, - slot: Slot, - state_root: Hash256, - spec: &ChainSpec, - ) -> Result<(), Error> { - let i = self.get_latest_state_roots_index(slot, spec)?; - Ok(self.latest_state_roots[i] = state_root) - } - - /// Generate a seed for the given `epoch`. - /// - /// Spec v0.4.0 - pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result { - let mut input = self - .get_randao_mix(epoch - spec.min_seed_lookahead, spec)? - .as_bytes() - .to_vec(); - - input.append( - &mut self - .get_active_index_root(epoch, spec) - .ok_or_else(|| Error::InsufficientIndexRoots)? - .as_bytes() - .to_vec(), - ); - - input.append(&mut int_to_bytes32(epoch.as_u64())); - - Ok(Hash256::from_slice(&hash(&input[..])[..])) - } - - /// Returns the beacon proposer index for the `slot`. - /// - /// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned. - /// - /// Spec v0.5.0 - pub fn get_beacon_proposer_index( - &self, - slot: Slot, - relative_epoch: RelativeEpoch, - spec: &ChainSpec, - ) -> Result { - let cache = self.cache(relative_epoch, spec)?; - - let committees = cache - .get_crosslink_committees_at_slot(slot, spec) - .ok_or_else(|| Error::SlotOutOfBounds)?; - - let epoch = slot.epoch(spec.slots_per_epoch); - - committees - .first() - .ok_or(Error::UnableToDetermineProducer) - .and_then(|first| { - let index = epoch - .as_usize() - .checked_rem(first.committee.len()) - .ok_or(Error::UnableToDetermineProducer)?; - Ok(first.committee[index]) - }) - } - - /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. - /// - /// Spec v0.4.0 - pub fn get_effective_balance( - &self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result { - let balance = self - .validator_balances - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?; - Ok(std::cmp::min(*balance, spec.max_deposit_amount)) - } - - /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. - /// - /// Spec v0.4.0 - pub fn get_delayed_activation_exit_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Epoch { - epoch + 1 + spec.activation_exit_delay - } - - /// Activate the validator of the given ``index``. - /// - /// Spec v0.5.0 - pub fn activate_validator( - &mut self, - validator_index: usize, - is_genesis: bool, - spec: &ChainSpec, - ) { - let current_epoch = self.current_epoch(spec); - - self.validator_registry[validator_index].activation_epoch = if is_genesis { - spec.genesis_epoch - } else { - self.get_delayed_activation_exit_epoch(current_epoch, spec) - } - } - - /// Initiate an exit for the validator of the given `index`. - /// - /// Spec v0.5.0 - pub fn initiate_validator_exit(&mut self, validator_index: usize) { - self.validator_registry[validator_index].initiated_exit = true; - } - - /// Exit the validator of the given `index`. - /// - /// Spec v0.4.0 - fn exit_validator(&mut self, validator_index: usize, spec: &ChainSpec) { - let current_epoch = self.current_epoch(spec); - let delayed_epoch = self.get_delayed_activation_exit_epoch(current_epoch, spec); - - if self.validator_registry[validator_index].exit_epoch <= delayed_epoch { - return; - } - - self.validator_registry[validator_index].exit_epoch = delayed_epoch; - } - - /// Slash the validator with index ``index``. - /// - /// Spec v0.5.0 - pub fn slash_validator( - &mut self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - - let validator = &self - .validator_registry - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?; - let effective_balance = self.get_effective_balance(validator_index, spec)?; - - // A validator that is withdrawn cannot be slashed. - // - // This constraint will be lifted in Phase 0. - if self.slot - >= validator - .withdrawable_epoch - .start_slot(spec.slots_per_epoch) - { - return Err(Error::ValidatorIsWithdrawable); - } - - self.exit_validator(validator_index, spec); - - self.increment_current_epoch_slashed_balances(effective_balance, spec)?; - - let whistleblower_index = - self.get_beacon_proposer_index(self.slot, RelativeEpoch::Current, spec)?; - let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient; - - safe_add_assign!( - self.validator_balances[whistleblower_index as usize], - whistleblower_reward - ); - safe_sub_assign!( - self.validator_balances[validator_index], - whistleblower_reward - ); - - self.validator_registry[validator_index].slashed = true; - - self.validator_registry[validator_index].withdrawable_epoch = - current_epoch + Epoch::from(spec.latest_slashed_exit_length); - - Ok(()) - } - - /// Increment `self.latest_slashed_balances` with a slashing from the current epoch. - /// - /// Spec v0.5.0. - fn increment_current_epoch_slashed_balances( - &mut self, - increment: u64, - spec: &ChainSpec, - ) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - - let slashed_balances_index = current_epoch.as_usize() % spec.latest_slashed_exit_length; - if slashed_balances_index >= self.latest_slashed_balances.len() { - return Err(Error::InsufficientSlashedBalances); - } - - self.latest_slashed_balances[slashed_balances_index] += increment; - - Ok(()) - } - - /// Initiate an exit for the validator of the given `index`. - /// - /// Spec v0.4.0 - pub fn prepare_validator_for_withdrawal(&mut self, validator_index: usize, spec: &ChainSpec) { - //TODO: we're not ANDing here, we're setting. Potentially wrong. - self.validator_registry[validator_index].withdrawable_epoch = - self.current_epoch(spec) + spec.min_validator_withdrawability_delay; - } - - /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an - /// attestation. - /// - /// Only reads the current epoch. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn get_attestation_duties( - &self, - validator_index: usize, - spec: &ChainSpec, - ) -> Result<&Option, Error> { - let cache = self.cache(RelativeEpoch::Current, spec)?; - - Ok(cache - .attestation_duties - .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?) - } - - /// Process slashings. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn process_slashings(&mut self, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - let active_validator_indices = self.get_active_validator_indices(current_epoch, spec)?; - let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; - - for (index, validator) in self.validator_registry.iter().enumerate() { - if validator.slashed - && (current_epoch - == validator.withdrawable_epoch - - Epoch::from(spec.latest_slashed_exit_length / 2)) - { - let epoch_index: usize = current_epoch.as_usize() % spec.latest_slashed_exit_length; - - let total_at_start = self.latest_slashed_balances - [(epoch_index + 1) % spec.latest_slashed_exit_length]; - let total_at_end = self.latest_slashed_balances[epoch_index]; - let total_penalities = total_at_end.saturating_sub(total_at_start); - - let effective_balance = self.get_effective_balance(index, spec)?; - let penalty = std::cmp::max( - effective_balance * std::cmp::min(total_penalities * 3, total_balance) - / total_balance, - effective_balance / spec.min_penalty_quotient, - ); - - safe_sub_assign!(self.validator_balances[index], penalty); - } - } - - Ok(()) - } - - /// Process the exit queue. - /// - /// Spec v0.4.0 - pub fn process_exit_queue(&mut self, spec: &ChainSpec) { - let current_epoch = self.current_epoch(spec); - - let eligible = |index: usize| { - let validator = &self.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 = (0..self.validator_registry.len()) - .filter(|i| eligible(*i)) - .collect(); - eligable_indices.sort_by_key(|i| self.validator_registry[*i].exit_epoch); - - for (withdrawn_so_far, index) in eligable_indices.iter().enumerate() { - if withdrawn_so_far as u64 >= spec.max_exit_dequeues_per_epoch { - break; - } - self.prepare_validator_for_withdrawal(*index, spec); - } - } - - /// Update validator registry, activating/exiting validators if possible. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn update_validator_registry(&mut self, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = self.current_epoch(spec); - let active_validator_indices = self.get_active_validator_indices(current_epoch, spec)?; - let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; - - let max_balance_churn = std::cmp::max( - spec.max_deposit_amount, - total_balance / (2 * spec.max_balance_churn_quotient), - ); - - let mut balance_churn = 0; - for index in 0..self.validator_registry.len() { - let validator = &self.validator_registry[index]; - - if (validator.activation_epoch == spec.far_future_epoch) - & (self.validator_balances[index] == spec.max_deposit_amount) - { - balance_churn += self.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - self.activate_validator(index, false, spec); - } - } - - let mut balance_churn = 0; - for index in 0..self.validator_registry.len() { - let validator = &self.validator_registry[index]; - - if (validator.exit_epoch == spec.far_future_epoch) & (validator.initiated_exit) { - balance_churn += self.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - - self.exit_validator(index, spec); - } - } - - self.validator_registry_update_epoch = current_epoch; - - Ok(()) - } - - /// Iterate through the validator registry and eject active validators with balance below - /// ``EJECTION_BALANCE``. - /// - /// Spec v0.5.0 - pub fn process_ejections(&mut self, 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 = self - .get_active_validator_indices(self.current_epoch(spec), spec)? - .iter() - .filter_map(|&i| { - if self.validator_balances[i as usize] < spec.ejection_balance { - Some(i) - } else { - None - } - }) - .collect(); - - for validator_index in exitable { - self.exit_validator(validator_index, spec) - } - - Ok(()) - } - - /// Return the combined effective balance of an array of validators. - /// - /// Spec v0.5.0 - pub fn get_total_balance( - &self, - validator_indices: &[usize], - spec: &ChainSpec, - ) -> Result { - validator_indices.iter().try_fold(0_u64, |acc, i| { - self.get_effective_balance(*i, spec) - .and_then(|bal| Ok(bal + acc)) - }) - } } impl From for Error { diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index ca8bcc70e..32d9a643e 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -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 { 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>, } 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, @@ -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, @@ -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, @@ -257,6 +276,7 @@ impl EpochCrosslinkCommitteesBuilder { }) } + /// Consumes the builder, returning a fully-build `EpochCrosslinkCommittee`. pub fn build(self, spec: &ChainSpec) -> Result { // 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) { diff --git a/eth2/types/src/beacon_state/epoch_cache/tests.rs b/eth2/types/src/beacon_state/epoch_cache/tests.rs index 10df635f2..5643776e2 100644 --- a/eth2/types/src/beacon_state/epoch_cache/tests.rs +++ b/eth2/types/src/beacon_state/epoch_cache/tests.rs @@ -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 = (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, diff --git a/eth2/types/src/beacon_state/pubkey_cache.rs b/eth2/types/src/beacon_state/pubkey_cache.rs index 340bdb311..4632a2d9c 100644 --- a/eth2/types/src/beacon_state/pubkey_cache.rs +++ b/eth2/types/src/beacon_state/pubkey_cache.rs @@ -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, } 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 diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index 0c6caaeee..65ea5c4d4 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -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, diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 7d506bc54..953a9508f 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -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; diff --git a/eth2/types/src/relative_epoch.rs b/eth2/types/src/relative_epoch.rs index 6c135b1a6..8f895e97a 100644 --- a/eth2/types/src/relative_epoch.rs +++ b/eth2/types/src/relative_epoch.rs @@ -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, diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 7a7a902de..473cd4166 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -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 diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index ee258e7fe..326858c31 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -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 diff --git a/eth2/types/src/transfer.rs b/eth2/types/src/transfer.rs index 1c9968702..2570d7b3f 100644 --- a/eth2/types/src/transfer.rs +++ b/eth2/types/src/transfer.rs @@ -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, diff --git a/eth2/types/src/validator_registry.rs b/eth2/types/src/validator_registry.rs deleted file mode 100644 index db35ae993..000000000 --- a/eth2/types/src/validator_registry.rs +++ /dev/null @@ -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 { - 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::>(); - - // 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::>() - ); - } - - 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::>(); - - // 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]); - } -}