diff --git a/Cargo.toml b/Cargo.toml index fdedbf6bb..26f7d293a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "eth2/attestation_validation", + "eth2/block_producer", "eth2/genesis", "eth2/naive_fork_choice", "eth2/spec", @@ -19,5 +20,4 @@ members = [ "beacon_node/beacon_chain", "protos", "validator_client", - "validator_client/block_proposer", ] diff --git a/validator_client/block_proposer/Cargo.toml b/eth2/block_producer/Cargo.toml similarity index 90% rename from validator_client/block_proposer/Cargo.toml rename to eth2/block_producer/Cargo.toml index 460ca863e..76ea78ea3 100644 --- a/validator_client/block_proposer/Cargo.toml +++ b/eth2/block_producer/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "block_proposer" +name = "block_producer" version = "0.1.0" authors = ["Paul Hauner "] edition = "2018" diff --git a/validator_client/block_proposer/src/lib.rs b/eth2/block_producer/src/lib.rs similarity index 100% rename from validator_client/block_proposer/src/lib.rs rename to eth2/block_producer/src/lib.rs diff --git a/validator_client/block_proposer/src/test_node.rs b/eth2/block_producer/src/test_node.rs similarity index 100% rename from validator_client/block_proposer/src/test_node.rs rename to eth2/block_producer/src/test_node.rs diff --git a/validator_client/block_proposer/src/traits.rs b/eth2/block_producer/src/traits.rs similarity index 100% rename from validator_client/block_proposer/src/traits.rs rename to eth2/block_producer/src/traits.rs diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index c23e6607a..c4f8b8f4a 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] +block_producer = { path = "../eth2/block_producer" } bls = { path = "../eth2/utils/bls" } clap = "2.32.0" dirs = "1.0.3" diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs deleted file mode 100644 index e0ea220b6..000000000 --- a/validator_client/src/block_producer/mod.rs +++ /dev/null @@ -1,255 +0,0 @@ -mod grpc; -mod service; -#[cfg(test)] -mod test_node; -pub mod traits; - -use self::traits::{BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError}; -use slot_clock::SlotClock; -use spec::ChainSpec; -use std::sync::{Arc, RwLock}; -use types::BeaconBlock; - -pub use self::service::BlockProducerService; - -#[derive(Debug, PartialEq)] -pub enum PollOutcome { - /// A new block was produced. - BlockProduced(u64), - /// A block was not produced as it would have been slashable. - SlashableBlockNotProduced(u64), - /// The validator duties did not require a block to be produced. - BlockProductionNotRequired(u64), - /// The duties for the present epoch were not found. - ProducerDutiesUnknown(u64), - /// The slot has already been processed, execution was skipped. - SlotAlreadyProcessed(u64), - /// The Beacon Node was unable to produce a block at that slot. - BeaconNodeUnableToProduceBlock(u64), -} - -#[derive(Debug, PartialEq)] -pub enum Error { - SlotClockError, - SlotUnknowable, - EpochMapPoisoned, - SlotClockPoisoned, - EpochLengthIsZero, - BeaconNodeError(BeaconNodeError), -} - -/// A polling state machine which performs block production duties, based upon some epoch duties -/// (`EpochDutiesMap`) and a concept of time (`SlotClock`). -/// -/// Ensures that messages are not slashable. -/// -/// Relies upon an external service to keep the `EpochDutiesMap` updated. -pub struct BlockProducer { - pub last_processed_slot: u64, - spec: Arc, - epoch_map: Arc, - slot_clock: Arc>, - beacon_node: Arc, -} - -impl BlockProducer { - /// Returns a new instance where `last_processed_slot == 0`. - pub fn new( - spec: Arc, - epoch_map: Arc, - slot_clock: Arc>, - beacon_node: Arc, - ) -> Self { - Self { - last_processed_slot: 0, - spec, - epoch_map, - slot_clock, - beacon_node, - } - } -} - -impl BlockProducer { - /// "Poll" to see if the validator is required to take any action. - /// - /// The slot clock will be read and any new actions undertaken. - pub fn poll(&mut self) -> Result { - let slot = self - .slot_clock - .read() - .map_err(|_| Error::SlotClockPoisoned)? - .present_slot() - .map_err(|_| Error::SlotClockError)? - .ok_or(Error::SlotUnknowable)?; - - let epoch = slot - .checked_div(self.spec.epoch_length) - .ok_or(Error::EpochLengthIsZero)?; - - // If this is a new slot. - if slot > self.last_processed_slot { - let is_block_production_slot = - match self.epoch_map.is_block_production_slot(epoch, slot) { - Ok(result) => result, - Err(DutiesReaderError::UnknownEpoch) => { - return Ok(PollOutcome::ProducerDutiesUnknown(slot)) - } - Err(DutiesReaderError::Poisoned) => return Err(Error::EpochMapPoisoned), - }; - - if is_block_production_slot { - self.last_processed_slot = slot; - - self.produce_block(slot) - } else { - Ok(PollOutcome::BlockProductionNotRequired(slot)) - } - } else { - Ok(PollOutcome::SlotAlreadyProcessed(slot)) - } - } - - /// Produce a block at some slot. - /// - /// Assumes that a block is required at this slot (does not check the duties). - /// - /// Ensures the message is not slashable. - /// - /// !!! UNSAFE !!! - /// - /// The slash-protection code is not yet implemented. There is zero protection against - /// slashing. - fn produce_block(&mut self, slot: u64) -> Result { - if let Some(block) = self.beacon_node.produce_beacon_block(slot)? { - if self.safe_to_produce(&block) { - let block = self.sign_block(block); - self.beacon_node.publish_beacon_block(block)?; - Ok(PollOutcome::BlockProduced(slot)) - } else { - Ok(PollOutcome::SlashableBlockNotProduced(slot)) - } - } else { - Ok(PollOutcome::BeaconNodeUnableToProduceBlock(slot)) - } - } - - /// Consumes a block, returning that block signed by the validators private key. - /// - /// Important: this function will not check to ensure the block is not slashable. This must be - /// done upstream. - fn sign_block(&mut self, block: BeaconBlock) -> BeaconBlock { - // TODO: sign the block - // https://github.com/sigp/lighthouse/issues/160 - self.store_produce(&block); - block - } - - /// Returns `true` if signing a block is safe (non-slashable). - /// - /// !!! UNSAFE !!! - /// - /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. - fn safe_to_produce(&self, _block: &BeaconBlock) -> bool { - // TODO: ensure the producer doesn't produce slashable blocks. - // https://github.com/sigp/lighthouse/issues/160 - true - } - - /// Record that a block was produced so that slashable votes may not be made in the future. - /// - /// !!! UNSAFE !!! - /// - /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. - fn store_produce(&mut self, _block: &BeaconBlock) { - // TODO: record this block production to prevent future slashings. - // https://github.com/sigp/lighthouse/issues/160 - } -} - -impl From for Error { - fn from(e: BeaconNodeError) -> Error { - Error::BeaconNodeError(e) - } -} - -#[cfg(test)] -mod tests { - use super::test_node::TestBeaconNode; - use super::*; - use crate::duties::EpochDuties; - use crate::duties::EpochDutiesMap; - use slot_clock::TestingSlotClock; - use types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - - // TODO: implement more thorough testing. - // https://github.com/sigp/lighthouse/issues/160 - // - // These tests should serve as a good example for future tests. - - #[test] - pub fn polling() { - let mut rng = XorShiftRng::from_seed([42; 16]); - - let spec = Arc::new(ChainSpec::foundation()); - let epoch_map = Arc::new(EpochDutiesMap::new()); - let slot_clock = Arc::new(RwLock::new(TestingSlotClock::new(0))); - let beacon_node = Arc::new(TestBeaconNode::default()); - - let mut block_producer = BlockProducer::new( - spec.clone(), - epoch_map.clone(), - slot_clock.clone(), - beacon_node.clone(), - ); - - // Configure responses from the BeaconNode. - beacon_node.set_next_produce_result(Ok(Some(BeaconBlock::random_for_test(&mut rng)))); - beacon_node.set_next_publish_result(Ok(true)); - - // Setup some valid duties for the validator - let produce_slot = 100; - let duties = EpochDuties { - block_production_slot: Some(produce_slot), - ..std::default::Default::default() - }; - let produce_epoch = produce_slot / spec.epoch_length; - epoch_map.insert(produce_epoch, duties); - - // One slot before production slot... - slot_clock.write().unwrap().set_slot(produce_slot - 1); - assert_eq!( - block_producer.poll(), - Ok(PollOutcome::BlockProductionNotRequired(produce_slot - 1)) - ); - - // On the produce slot... - slot_clock.write().unwrap().set_slot(produce_slot); - assert_eq!( - block_producer.poll(), - Ok(PollOutcome::BlockProduced(produce_slot)) - ); - - // Trying the same produce slot again... - slot_clock.write().unwrap().set_slot(produce_slot); - assert_eq!( - block_producer.poll(), - Ok(PollOutcome::SlotAlreadyProcessed(produce_slot)) - ); - - // One slot after the produce slot... - slot_clock.write().unwrap().set_slot(produce_slot + 1); - assert_eq!( - block_producer.poll(), - Ok(PollOutcome::BlockProductionNotRequired(produce_slot + 1)) - ); - - // In an epoch without known duties... - let slot = (produce_epoch + 1) * spec.epoch_length; - slot_clock.write().unwrap().set_slot(slot); - assert_eq!( - block_producer.poll(), - Ok(PollOutcome::ProducerDutiesUnknown(slot)) - ); - } -} diff --git a/validator_client/src/block_producer/test_node.rs b/validator_client/src/block_producer/test_node.rs deleted file mode 100644 index e99613e8f..000000000 --- a/validator_client/src/block_producer/test_node.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::traits::{BeaconNode, BeaconNodeError}; -use std::sync::RwLock; -use types::BeaconBlock; - -type ProduceResult = Result, BeaconNodeError>; -type PublishResult = Result; - -/// A test-only struct used to simulate a Beacon Node. -#[derive(Default)] -pub struct TestBeaconNode { - pub produce_input: RwLock>, - pub produce_result: RwLock>, - pub publish_input: RwLock>, - pub publish_result: RwLock>, -} - -impl TestBeaconNode { - /// Set the result to be returned when `produce_beacon_block` is called. - pub fn set_next_produce_result(&self, result: ProduceResult) { - *self.produce_result.write().unwrap() = Some(result); - } - - /// Set the result to be returned when `publish_beacon_block` is called. - pub fn set_next_publish_result(&self, result: PublishResult) { - *self.publish_result.write().unwrap() = Some(result); - } -} - -impl BeaconNode for TestBeaconNode { - /// Returns the value specified by the `set_next_produce_result`. - fn produce_beacon_block(&self, slot: u64) -> ProduceResult { - *self.produce_input.write().unwrap() = Some(slot); - match *self.produce_result.read().unwrap() { - Some(ref r) => r.clone(), - None => panic!("TestBeaconNode: produce_result == None"), - } - } - - /// Returns the value specified by the `set_next_publish_result`. - fn publish_beacon_block(&self, block: BeaconBlock) -> PublishResult { - *self.publish_input.write().unwrap() = Some(block); - match *self.publish_result.read().unwrap() { - Some(ref r) => r.clone(), - None => panic!("TestBeaconNode: publish_result == None"), - } - } -} diff --git a/validator_client/src/block_producer/traits.rs b/validator_client/src/block_producer/traits.rs deleted file mode 100644 index e16af2460..000000000 --- a/validator_client/src/block_producer/traits.rs +++ /dev/null @@ -1,28 +0,0 @@ -use types::BeaconBlock; - -#[derive(Debug, PartialEq, Clone)] -pub enum BeaconNodeError { - RemoteFailure(String), - DecodeFailure, -} - -/// Defines the methods required to produce and publish blocks on a Beacon Node. -pub trait BeaconNode: Send + Sync { - /// Request that the node produces a block. - /// - /// Returns Ok(None) if the Beacon Node is unable to produce at the given slot. - fn produce_beacon_block(&self, slot: u64) -> Result, BeaconNodeError>; - /// Request that the node publishes a block. - /// - /// Returns `true` if the publish was sucessful. - fn publish_beacon_block(&self, block: BeaconBlock) -> Result; -} - -pub enum DutiesReaderError { - UnknownEpoch, - Poisoned, -} - -pub trait DutiesReader: Send + Sync { - fn is_block_production_slot(&self, epoch: u64, slot: u64) -> Result; -} diff --git a/validator_client/src/block_producer/grpc.rs b/validator_client/src/block_producer_service/grpc.rs similarity index 86% rename from validator_client/src/block_producer/grpc.rs rename to validator_client/src/block_producer_service/grpc.rs index 20ced3d0c..b3bc8f5cc 100644 --- a/validator_client/src/block_producer/grpc.rs +++ b/validator_client/src/block_producer_service/grpc.rs @@ -1,12 +1,25 @@ -use super::traits::{BeaconNode, BeaconNodeError}; +use block_producer::{BeaconNode, BeaconNodeError}; use protos::services::{ BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, }; use protos::services_grpc::BeaconBlockServiceClient; use ssz::{ssz_encode, Decodable}; +use std::sync::Arc; use types::{BeaconBlock, BeaconBlockBody, Hash256, Signature}; -impl BeaconNode for BeaconBlockServiceClient { +/// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be +/// implemented upon it. +pub struct BeaconBlockGrpcClient { + client: Arc, +} + +impl BeaconBlockGrpcClient { + pub fn new(client: Arc) -> Self { + Self { client } + } +} + +impl BeaconNode for BeaconBlockGrpcClient { /// Request a Beacon Node (BN) to produce a new block at the supplied slot. /// /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the @@ -16,6 +29,7 @@ impl BeaconNode for BeaconBlockServiceClient { req.set_slot(slot); let reply = self + .client .produce_beacon_block(&req) .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; @@ -69,6 +83,7 @@ impl BeaconNode for BeaconBlockServiceClient { req.set_block(grpc_block); let reply = self + .client .publish_beacon_block(&req) .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; diff --git a/validator_client/src/block_producer_service/mod.rs b/validator_client/src/block_producer_service/mod.rs new file mode 100644 index 000000000..a4ddf760a --- /dev/null +++ b/validator_client/src/block_producer_service/mod.rs @@ -0,0 +1,5 @@ +mod grpc; +mod service; + +pub use self::grpc::BeaconBlockGrpcClient; +pub use self::service::BlockProducerService; diff --git a/validator_client/src/block_producer/service.rs b/validator_client/src/block_producer_service/service.rs similarity index 93% rename from validator_client/src/block_producer/service.rs rename to validator_client/src/block_producer_service/service.rs index 822df76c1..6652058c8 100644 --- a/validator_client/src/block_producer/service.rs +++ b/validator_client/src/block_producer_service/service.rs @@ -1,6 +1,8 @@ -use super::traits::{BeaconNode, DutiesReader}; -use super::{BlockProducer, PollOutcome as BlockProducerPollOutcome, SlotClock}; +use block_producer::{ + BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, +}; use slog::{error, info, warn, Logger}; +use slot_clock::SlotClock; use std::time::Duration; pub struct BlockProducerService { diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 8e7019533..8484ad80c 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -5,7 +5,7 @@ mod test_node; mod traits; use self::traits::{BeaconNode, BeaconNodeError}; -use super::block_producer::traits::{DutiesReader, DutiesReaderError}; +use block_producer::{DutiesReader, DutiesReaderError}; use bls::PublicKey; use slot_clock::SlotClock; use spec::ChainSpec; diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index c1775f826..65338cfe7 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,6 +1,7 @@ +use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; -use crate::block_producer::{BlockProducer, BlockProducerService}; use crate::config::ClientConfig; +use block_producer::BlockProducer; use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; @@ -12,7 +13,7 @@ use std::path::PathBuf; use std::sync::{Arc, RwLock}; use std::thread; -mod block_producer; +mod block_producer_service; mod config; mod duties; @@ -141,7 +142,7 @@ fn main() { let duties_map = duties_map.clone(); let slot_clock = slot_clock.clone(); let log = log.clone(); - let client = beacon_block_grpc_client.clone(); + let client = Arc::new(BeaconBlockGrpcClient::new(beacon_block_grpc_client.clone())); thread::spawn(move || { let block_producer = BlockProducer::new(spec, duties_map, slot_clock, client); let mut block_producer_service = BlockProducerService {