diff --git a/validator_client/src/block_producer/block_producer.rs b/validator_client/src/block_producer/block_producer.rs deleted file mode 100644 index e71e6cd4b..000000000 --- a/validator_client/src/block_producer/block_producer.rs +++ /dev/null @@ -1,219 +0,0 @@ -use super::beacon_block_node::{BeaconBlockNode, BeaconBlockNodeError}; -use crate::signer::Signer; -use ssz::{SignedRoot, TreeHash}; -use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; - -#[derive(Debug, PartialEq)] -pub enum Error { - SlotClockError, - SlotUnknowable, - EpochMapPoisoned, - SlotClockPoisoned, - EpochLengthIsZero, - BeaconBlockNodeError(BeaconBlockNodeError), -} - -#[derive(Debug, PartialEq)] -pub enum ValidatorEvent { - /// A new block was produced. - BlockProduced(Slot), - /// A block was not produced as it would have been slashable. - SlashableBlockNotProduced(Slot), - /// The Beacon Node was unable to produce a block at that slot. - BeaconNodeUnableToProduceBlock(Slot), - /// The signer failed to sign the message. - SignerRejection(Slot), - /// The public key for this validator is not an active validator. - ValidatorIsUnknown(Slot), -} - -/// This struct contains the logic for requesting and signing beacon blocks for a validator. The -/// validator can abstractly sign via the Signer trait object. -pub struct BlockProducer { - /// The current fork. - pub fork: Fork, - /// The current slot to produce a block for. - pub slot: Slot, - /// The current epoch. - pub spec: Arc, - /// The beacon node to connect to. - pub beacon_node: Arc, - /// The signer to sign the block. - pub signer: Arc, -} - -impl BlockProducer { - /// 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) -> Result { - let epoch = self.slot.epoch(self.spec.slots_per_epoch); - - let randao_reveal = { - let message = epoch.hash_tree_root(); - let randao_reveal = match self.signer.sign_randao_reveal( - &message, - self.spec.get_domain(epoch, Domain::Randao, &self.fork), - ) { - None => return Ok(ValidatorEvent::SignerRejection(self.slot)), - Some(signature) => signature, - }; - randao_reveal - }; - - if let Some(block) = self - .beacon_node - .produce_beacon_block(self.slot, &randao_reveal)? - { - if self.safe_to_produce(&block) { - let domain = self.spec.get_domain(epoch, Domain::BeaconBlock, &self.fork); - if let Some(block) = self.sign_block(block, domain) { - self.beacon_node.publish_beacon_block(block)?; - Ok(ValidatorEvent::BlockProduced(self.slot)) - } else { - Ok(ValidatorEvent::SignerRejection(self.slot)) - } - } else { - Ok(ValidatorEvent::SlashableBlockNotProduced(self.slot)) - } - } else { - Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(self.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, mut block: BeaconBlock, domain: u64) -> Option { - self.store_produce(&block); - - match self - .signer - .sign_block_proposal(&block.signed_root()[..], domain) - { - None => None, - Some(signature) => { - block.signature = signature; - Some(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: BeaconBlockNodeError) -> Error { - Error::BeaconBlockNodeError(e) - } -} - -/* Old tests - Re-work for new logic -#[cfg(test)] -mod tests { - use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode}; - use super::*; - use slot_clock::TestingSlotClock; - use types::{ - test_utils::{SeedableRng, TestRandom, XorShiftRng}, - Keypair, - }; - - // 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 slot_clock = Arc::new(TestingSlotClock::new(0)); - let beacon_node = Arc::new(SimulatedBeaconNode::default()); - let signer = Arc::new(LocalSigner::new(Keypair::random())); - - let mut epoch_map = EpochMap::new(spec.slots_per_epoch); - let produce_slot = Slot::new(100); - let produce_epoch = produce_slot.epoch(spec.slots_per_epoch); - epoch_map.map.insert(produce_epoch, produce_slot); - let epoch_map = Arc::new(epoch_map); - - let mut block_proposer = BlockProducer::new( - spec.clone(), - epoch_map.clone(), - slot_clock.clone(), - beacon_node.clone(), - signer.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(PublishOutcome::ValidBlock)); - - // One slot before production slot... - slot_clock.set_slot(produce_slot.as_u64() - 1); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::BlockProductionNotRequired(produce_slot - 1)) - ); - - // On the produce slot... - slot_clock.set_slot(produce_slot.as_u64()); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::BlockProduced(produce_slot.into())) - ); - - // Trying the same produce slot again... - slot_clock.set_slot(produce_slot.as_u64()); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::SlotAlreadyProcessed(produce_slot)) - ); - - // One slot after the produce slot... - slot_clock.set_slot(produce_slot.as_u64() + 1); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::BlockProductionNotRequired(produce_slot + 1)) - ); - - // In an epoch without known duties... - let slot = (produce_epoch.as_u64() + 1) * spec.slots_per_epoch; - slot_clock.set_slot(slot); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::ProducerDutiesUnknown(Slot::new(slot))) - ); - } -} -*/ diff --git a/validator_client/src/block_producer/grpc.rs b/validator_client/src/block_producer/grpc.rs new file mode 100644 index 000000000..23477ece1 --- /dev/null +++ b/validator_client/src/block_producer/grpc.rs @@ -0,0 +1,86 @@ +use super::beacon_block_node::*; +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, Signature, Slot}; + +/// 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 BeaconBlockNode 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 + /// BN is unable to find a parent block. + fn produce_beacon_block( + &self, + slot: Slot, + randao_reveal: &Signature, + ) -> Result, BeaconBlockNodeError> { + // request a beacon block from the node + let mut req = ProduceBeaconBlockRequest::new(); + req.set_slot(slot.as_u64()); + req.set_randao_reveal(ssz_encode(randao_reveal)); + + //TODO: Determine if we want an explicit timeout + let reply = self + .client + .produce_beacon_block(&req) + .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + + // format the reply + if reply.has_block() { + let block = reply.get_block(); + let ssz = block.get_ssz(); + + let (block, _i) = BeaconBlock::ssz_decode(&ssz, 0) + .map_err(|_| BeaconBlockNodeError::DecodeFailure)?; + + Ok(Some(block)) + } else { + Ok(None) + } + } + + /// Request a Beacon Node (BN) to publish a block. + /// + /// Generally, this will be called after a `produce_beacon_block` call with a block that has + /// been completed (signed) by the validator client. + fn publish_beacon_block( + &self, + block: BeaconBlock, + ) -> Result { + let mut req = PublishBeaconBlockRequest::new(); + + let ssz = ssz_encode(&block); + + let mut grpc_block = GrpcBeaconBlock::new(); + grpc_block.set_ssz(ssz); + + req.set_block(grpc_block); + + let reply = self + .client + .publish_beacon_block(&req) + .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + + if reply.get_success() { + Ok(PublishOutcome::ValidBlock) + } else { + // TODO: distinguish between different errors + Ok(PublishOutcome::InvalidBlock("Publish failed".to_string())) + } + } +} diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index fe34f627d..8297469ba 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -1,89 +1,223 @@ mod beacon_block_node; -mod block_producer; +mod grpc; -use self::beacon_block_node::*; -use protos::services::{ - BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, -}; -use protos::services_grpc::BeaconBlockServiceClient; -use ssz::{ssz_encode, Decodable}; +use self::beacon_block_node::{BeaconBlockNode, BeaconBlockNodeError}; +pub use self::grpc::BeaconBlockGrpcClient; +use crate::signer::Signer; +use ssz::{SignedRoot, TreeHash}; use std::sync::Arc; -use types::{BeaconBlock, Signature, Slot}; +use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; -/// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be -/// implemented upon it. -pub struct BeaconBlockGrpcClient { - client: Arc, +#[derive(Debug, PartialEq)] +pub enum Error { + SlotClockError, + SlotUnknowable, + EpochMapPoisoned, + SlotClockPoisoned, + EpochLengthIsZero, + BeaconBlockNodeError(BeaconBlockNodeError), } -impl BeaconBlockGrpcClient { - pub fn new(client: Arc) -> Self { - Self { client } - } +#[derive(Debug, PartialEq)] +pub enum ValidatorEvent { + /// A new block was produced. + BlockProduced(Slot), + /// A block was not produced as it would have been slashable. + SlashableBlockNotProduced(Slot), + /// The Beacon Node was unable to produce a block at that slot. + BeaconNodeUnableToProduceBlock(Slot), + /// The signer failed to sign the message. + SignerRejection(Slot), + /// The public key for this validator is not an active validator. + ValidatorIsUnknown(Slot), } -impl BeaconBlockNode for BeaconBlockGrpcClient { - /// Request a Beacon Node (BN) to produce a new block at the supplied slot. +/// This struct contains the logic for requesting and signing beacon blocks for a validator. The +/// validator can abstractly sign via the Signer trait object. +pub struct BlockProducer { + /// The current fork. + pub fork: Fork, + /// The current slot to produce a block for. + pub slot: Slot, + /// The current epoch. + pub spec: Arc, + /// The beacon node to connect to. + pub beacon_node: Arc, + /// The signer to sign the block. + pub signer: Arc, +} + +impl BlockProducer { + /// Produce a block at some slot. /// - /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the - /// BN is unable to find a parent block. - fn produce_beacon_block( - &self, - slot: Slot, - randao_reveal: &Signature, - ) -> Result, BeaconBlockNodeError> { - // request a beacon block from the node - let mut req = ProduceBeaconBlockRequest::new(); - req.set_slot(slot.as_u64()); - req.set_randao_reveal(ssz_encode(randao_reveal)); + /// 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) -> Result { + let epoch = self.slot.epoch(self.spec.slots_per_epoch); - //TODO: Determine if we want an explicit timeout - let reply = self - .client - .produce_beacon_block(&req) - .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + let randao_reveal = { + let message = epoch.hash_tree_root(); + let randao_reveal = match self.signer.sign_randao_reveal( + &message, + self.spec.get_domain(epoch, Domain::Randao, &self.fork), + ) { + None => return Ok(ValidatorEvent::SignerRejection(self.slot)), + Some(signature) => signature, + }; + randao_reveal + }; - // format the reply - if reply.has_block() { - let block = reply.get_block(); - let ssz = block.get_ssz(); - - let (block, _i) = BeaconBlock::ssz_decode(&ssz, 0) - .map_err(|_| BeaconBlockNodeError::DecodeFailure)?; - - Ok(Some(block)) + if let Some(block) = self + .beacon_node + .produce_beacon_block(self.slot, &randao_reveal)? + { + if self.safe_to_produce(&block) { + let domain = self.spec.get_domain(epoch, Domain::BeaconBlock, &self.fork); + if let Some(block) = self.sign_block(block, domain) { + self.beacon_node.publish_beacon_block(block)?; + Ok(ValidatorEvent::BlockProduced(self.slot)) + } else { + Ok(ValidatorEvent::SignerRejection(self.slot)) + } + } else { + Ok(ValidatorEvent::SlashableBlockNotProduced(self.slot)) + } } else { - Ok(None) + Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(self.slot)) } } - /// Request a Beacon Node (BN) to publish a block. + /// Consumes a block, returning that block signed by the validators private key. /// - /// Generally, this will be called after a `produce_beacon_block` call with a block that has - /// been completed (signed) by the validator client. - fn publish_beacon_block( - &self, - block: BeaconBlock, - ) -> Result { - let mut req = PublishBeaconBlockRequest::new(); + /// Important: this function will not check to ensure the block is not slashable. This must be + /// done upstream. + fn sign_block(&mut self, mut block: BeaconBlock, domain: u64) -> Option { + self.store_produce(&block); - let ssz = ssz_encode(&block); - - let mut grpc_block = GrpcBeaconBlock::new(); - grpc_block.set_ssz(ssz); - - req.set_block(grpc_block); - - let reply = self - .client - .publish_beacon_block(&req) - .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; - - if reply.get_success() { - Ok(PublishOutcome::ValidBlock) - } else { - // TODO: distinguish between different errors - Ok(PublishOutcome::InvalidBlock("Publish failed".to_string())) + match self + .signer + .sign_block_proposal(&block.signed_root()[..], domain) + { + None => None, + Some(signature) => { + block.signature = signature; + Some(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: BeaconBlockNodeError) -> Error { + Error::BeaconBlockNodeError(e) + } +} + +/* Old tests - Re-work for new logic +#[cfg(test)] +mod tests { + use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode}; + use super::*; + use slot_clock::TestingSlotClock; + use types::{ + test_utils::{SeedableRng, TestRandom, XorShiftRng}, + Keypair, + }; + + // 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 slot_clock = Arc::new(TestingSlotClock::new(0)); + let beacon_node = Arc::new(SimulatedBeaconNode::default()); + let signer = Arc::new(LocalSigner::new(Keypair::random())); + + let mut epoch_map = EpochMap::new(spec.slots_per_epoch); + let produce_slot = Slot::new(100); + let produce_epoch = produce_slot.epoch(spec.slots_per_epoch); + epoch_map.map.insert(produce_epoch, produce_slot); + let epoch_map = Arc::new(epoch_map); + + let mut block_proposer = BlockProducer::new( + spec.clone(), + epoch_map.clone(), + slot_clock.clone(), + beacon_node.clone(), + signer.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(PublishOutcome::ValidBlock)); + + // One slot before production slot... + slot_clock.set_slot(produce_slot.as_u64() - 1); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProductionNotRequired(produce_slot - 1)) + ); + + // On the produce slot... + slot_clock.set_slot(produce_slot.as_u64()); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProduced(produce_slot.into())) + ); + + // Trying the same produce slot again... + slot_clock.set_slot(produce_slot.as_u64()); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::SlotAlreadyProcessed(produce_slot)) + ); + + // One slot after the produce slot... + slot_clock.set_slot(produce_slot.as_u64() + 1); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProductionNotRequired(produce_slot + 1)) + ); + + // In an epoch without known duties... + let slot = (produce_epoch.as_u64() + 1) * spec.slots_per_epoch; + slot_clock.set_slot(slot); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::ProducerDutiesUnknown(Slot::new(slot))) + ); + } +} +*/ diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 6f02cf6ee..b3a488fe0 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -8,6 +8,7 @@ mod signer; use crate::config::Config as ValidatorClientConfig; use clap::{App, Arg}; +use protos::services_grpc::ValidatorServiceClient; use service::Service as ValidatorService; use slog::{error, info, o, Drain}; @@ -53,7 +54,8 @@ fn main() { .expect("Unable to build a configuration for the validator client."); // start the validator service. - match ValidatorService::start(config, log.clone()) { + // this specifies the GRPC type to use as the duty manager beacon node. + match ValidatorService::::start(config, log.clone()) { Ok(_) => info!(log, "Validator client shutdown successfully."), Err(e) => error!(log, "Validator exited due to: {}", e.to_string()), } diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 398f6d777..c8874a1f6 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -9,8 +9,7 @@ /// data from the beacon node and performs the signing before publishing the block to the beacon /// node. use crate::attester_service::{AttestationGrpcClient, AttesterService}; -use crate::block_producer::BlockProducer; -use crate::block_producer_service::BeaconBlockGrpcClient; +use crate::block_producer::{BeaconBlockGrpcClient, BlockProducer}; use crate::config::Config as ValidatorConfig; use crate::duties::{BeaconNodeDuties, DutiesManager, EpochDutiesMap, UpdateOutcome}; use crate::error as error_chain; @@ -40,6 +39,7 @@ use types::{ChainSpec, Epoch, Fork, Slot}; /// The validator service. This is the main thread that executes and maintains validator /// duties. +//TODO: Generalize the BeaconNode types to use testing pub struct Service { /// The node we currently connected to. connected_node_version: String,