diff --git a/eth2/block_producer/src/lib.rs b/eth2/block_producer/src/lib.rs index b2f17852b6..fc06ffa46f 100644 --- a/eth2/block_producer/src/lib.rs +++ b/eth2/block_producer/src/lib.rs @@ -1,11 +1,10 @@ -#[cfg(test)] -mod test_node; +pub mod test_utils; mod traits; use slot_clock::SlotClock; use spec::ChainSpec; use std::sync::{Arc, RwLock}; -use types::BeaconBlock; +use types::{BeaconBlock, Hash256, ProposalSignedData}; pub use self::traits::{BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, Signer}; @@ -23,6 +22,8 @@ pub enum PollOutcome { SlotAlreadyProcessed(u64), /// The Beacon Node was unable to produce a block at that slot. BeaconNodeUnableToProduceBlock(u64), + /// The signer failed to sign the message. + SignerRejection(u64), } #[derive(Debug, PartialEq)] @@ -123,9 +124,12 @@ impl BlockProducer 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)) + if let Some(block) = self.sign_block(block) { + self.beacon_node.publish_beacon_block(block)?; + Ok(PollOutcome::BlockProduced(slot)) + } else { + Ok(PollOutcome::SignerRejection(slot)) + } } else { Ok(PollOutcome::SlashableBlockNotProduced(slot)) } @@ -138,11 +142,30 @@ impl BlockProducer BeaconBlock { - // TODO: sign the block - // https://github.com/sigp/lighthouse/issues/160 + fn sign_block(&mut self, mut block: BeaconBlock) -> Option { self.store_produce(&block); - block + + let proposal_root = { + let block_without_signature_root = { + let mut block_without_signature = block.clone(); + block_without_signature.signature = self.spec.empty_signature.clone(); + block_without_signature.canonical_root() + }; + let proposal = ProposalSignedData { + slot: block.slot, + shard: self.spec.beacon_chain_shard_number, + block_root: block_without_signature_root, + }; + hash_tree_root(&proposal) + }; + + match self.signer.bls_sign(&proposal_root[..]) { + None => None, + Some(signature) => { + block.signature = signature; + Some(block) + } + } } /// Returns `true` if signing a block is safe (non-slashable). @@ -167,6 +190,11 @@ impl BlockProducer(_input: &T) -> Hash256 { + // TODO: stubbed out. + Hash256::zero() +} + impl From for Error { fn from(e: BeaconNodeError) -> Error { Error::BeaconNodeError(e) @@ -175,13 +203,12 @@ impl From for Error { #[cfg(test)] mod tests { - use super::test_node::TestBeaconNode; + use super::test_utils::{TestBeaconNode, TestEpochMap, TestSigner}; use super::*; use slot_clock::TestingSlotClock; - use std::collections::HashMap; use types::{ test_utils::{SeedableRng, TestRandom, XorShiftRng}, - Signature, + Keypair, }; // TODO: implement more thorough testing. @@ -189,31 +216,6 @@ mod tests { // // These tests should serve as a good example for future tests. - type EpochMap = HashMap; - - impl DutiesReader for EpochMap { - fn is_block_production_slot( - &self, - epoch: u64, - slot: u64, - ) -> Result { - match self.get(&epoch) { - Some(s) if *s == slot => Ok(true), - Some(s) if *s != slot => Ok(false), - _ => Err(DutiesReaderError::UnknownEpoch), - } - } - } - - struct TestSigner(); - - impl Signer for TestSigner { - fn bls_sign(_message: &[u8]) -> Option { - let mut rng = XorShiftRng::from_seed([42; 16]); - Some(Signature::random_for_test(&mut rng)) - } - } - #[test] pub fn polling() { let mut rng = XorShiftRng::from_seed([42; 16]); @@ -221,9 +223,9 @@ mod tests { let spec = Arc::new(ChainSpec::foundation()); let slot_clock = Arc::new(RwLock::new(TestingSlotClock::new(0))); let beacon_node = Arc::new(TestBeaconNode::default()); - let signer = Arc::new(TestSigner()); + let signer = Arc::new(TestSigner::new(Keypair::random())); - let mut epoch_map = EpochMap::new(); + let mut epoch_map = TestEpochMap::new(); let produce_slot = 100; let produce_epoch = produce_slot / spec.epoch_length; epoch_map.insert(produce_epoch, produce_slot); diff --git a/eth2/block_producer/src/test_node.rs b/eth2/block_producer/src/test_utils/beacon_node.rs similarity index 97% rename from eth2/block_producer/src/test_node.rs rename to eth2/block_producer/src/test_utils/beacon_node.rs index e99613e8f3..bca01d9c7d 100644 --- a/eth2/block_producer/src/test_node.rs +++ b/eth2/block_producer/src/test_utils/beacon_node.rs @@ -1,4 +1,4 @@ -use super::traits::{BeaconNode, BeaconNodeError}; +use crate::traits::{BeaconNode, BeaconNodeError}; use std::sync::RwLock; use types::BeaconBlock; diff --git a/eth2/block_producer/src/test_utils/epoch_map.rs b/eth2/block_producer/src/test_utils/epoch_map.rs new file mode 100644 index 0000000000..290660afd8 --- /dev/null +++ b/eth2/block_producer/src/test_utils/epoch_map.rs @@ -0,0 +1,14 @@ +use crate::{DutiesReader, DutiesReaderError}; +use std::collections::HashMap; + +pub type TestEpochMap = HashMap; + +impl DutiesReader for TestEpochMap { + fn is_block_production_slot(&self, epoch: u64, slot: u64) -> Result { + match self.get(&epoch) { + Some(s) if *s == slot => Ok(true), + Some(s) if *s != slot => Ok(false), + _ => Err(DutiesReaderError::UnknownEpoch), + } + } +} diff --git a/eth2/block_producer/src/test_utils/mod.rs b/eth2/block_producer/src/test_utils/mod.rs new file mode 100644 index 0000000000..0dd384b125 --- /dev/null +++ b/eth2/block_producer/src/test_utils/mod.rs @@ -0,0 +1,7 @@ +mod beacon_node; +mod epoch_map; +mod signer; + +pub use self::beacon_node::TestBeaconNode; +pub use self::epoch_map::TestEpochMap; +pub use self::signer::TestSigner; diff --git a/eth2/block_producer/src/test_utils/signer.rs b/eth2/block_producer/src/test_utils/signer.rs new file mode 100644 index 0000000000..d43c0feb0c --- /dev/null +++ b/eth2/block_producer/src/test_utils/signer.rs @@ -0,0 +1,31 @@ +use crate::traits::Signer; +use std::sync::RwLock; +use types::{Keypair, Signature}; + +/// A test-only struct used to simulate a Beacon Node. +pub struct TestSigner { + keypair: Keypair, + should_sign: RwLock, +} + +impl TestSigner { + /// Produce a new TestSigner with signing enabled by default. + pub fn new(keypair: Keypair) -> Self { + Self { + keypair, + should_sign: RwLock::new(true), + } + } + + /// If set to `false`, the service will refuse to sign all messages. Otherwise, all messages + /// will be signed. + pub fn enable_signing(&self, enabled: bool) { + *self.should_sign.write().unwrap() = enabled; + } +} + +impl Signer for TestSigner { + fn bls_sign(&self, message: &[u8]) -> Option { + Some(Signature::new(message, &self.keypair.sk)) + } +} diff --git a/eth2/block_producer/src/traits.rs b/eth2/block_producer/src/traits.rs index c718064d2c..c9716701b2 100644 --- a/eth2/block_producer/src/traits.rs +++ b/eth2/block_producer/src/traits.rs @@ -18,16 +18,18 @@ pub trait BeaconNode: Send + Sync { fn publish_beacon_block(&self, block: BeaconBlock) -> Result; } +#[derive(Debug, PartialEq, Clone)] pub enum DutiesReaderError { UnknownEpoch, Poisoned, } -/// Provides methods for a validator to determine their responsibilities for some slot. +/// Informs a validator of their duties (e.g., block production). pub trait DutiesReader: Send + Sync { fn is_block_production_slot(&self, epoch: u64, slot: u64) -> Result; } +/// Signs message using an internally-maintained private key. pub trait Signer { - fn bls_sign(message: &[u8]) -> Option; + fn bls_sign(&self, message: &[u8]) -> Option; } diff --git a/validator_client/src/block_producer_service/service.rs b/validator_client/src/block_producer_service/service.rs index 6652058c8f..3966d42f99 100644 --- a/validator_client/src/block_producer_service/service.rs +++ b/validator_client/src/block_producer_service/service.rs @@ -1,17 +1,17 @@ use block_producer::{ - BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, + BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, Signer, }; use slog::{error, info, warn, Logger}; use slot_clock::SlotClock; use std::time::Duration; -pub struct BlockProducerService { - pub block_producer: BlockProducer, +pub struct BlockProducerService { + pub block_producer: BlockProducer, pub poll_interval_millis: u64, pub log: Logger, } -impl BlockProducerService { +impl BlockProducerService { /// Run a loop which polls the block producer each `poll_interval_millis` millseconds. /// /// Logs the results of the polls. @@ -39,6 +39,9 @@ impl BlockProducerService Ok(BlockProducerPollOutcome::BeaconNodeUnableToProduceBlock(slot)) => { error!(self.log, "Beacon node unable to produce block"; "slot" => slot) } + Ok(BlockProducerPollOutcome::SignerRejection(slot)) => { + error!(self.log, "The cryptographic signer refused to sign the block"; "slot" => slot) + } }; std::thread::sleep(Duration::from_millis(self.poll_interval_millis)); diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 65338cfe7e..cd626b2fc8 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,7 +1,7 @@ use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; use crate::config::ClientConfig; -use block_producer::BlockProducer; +use block_producer::{test_utils::TestSigner, BlockProducer}; use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; @@ -139,12 +139,14 @@ fn main() { // Spawn a new thread to perform block production for the validator. let producer_thread = { let spec = spec.clone(); + let signer = Arc::new(TestSigner::new(keypair.clone())); let duties_map = duties_map.clone(); let slot_clock = slot_clock.clone(); let log = log.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 block_producer = + BlockProducer::new(spec, duties_map, slot_clock, client, signer); let mut block_producer_service = BlockProducerService { block_producer, poll_interval_millis,