Add Signer to validator client

This commit is contained in:
Paul Hauner 2019-01-24 11:50:34 +11:00
parent 2bcce37b3c
commit 8c0e634aa4
No known key found for this signature in database
GPG Key ID: D362883A9218FCC6
8 changed files with 110 additions and 49 deletions

View File

@ -1,11 +1,10 @@
#[cfg(test)] pub mod test_utils;
mod test_node;
mod traits; mod traits;
use slot_clock::SlotClock; use slot_clock::SlotClock;
use spec::ChainSpec; use spec::ChainSpec;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use types::BeaconBlock; use types::{BeaconBlock, Hash256, ProposalSignedData};
pub use self::traits::{BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, Signer}; pub use self::traits::{BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, Signer};
@ -23,6 +22,8 @@ pub enum PollOutcome {
SlotAlreadyProcessed(u64), SlotAlreadyProcessed(u64),
/// The Beacon Node was unable to produce a block at that slot. /// The Beacon Node was unable to produce a block at that slot.
BeaconNodeUnableToProduceBlock(u64), BeaconNodeUnableToProduceBlock(u64),
/// The signer failed to sign the message.
SignerRejection(u64),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -123,9 +124,12 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
fn produce_block(&mut self, slot: u64) -> Result<PollOutcome, Error> { fn produce_block(&mut self, slot: u64) -> Result<PollOutcome, Error> {
if let Some(block) = self.beacon_node.produce_beacon_block(slot)? { if let Some(block) = self.beacon_node.produce_beacon_block(slot)? {
if self.safe_to_produce(&block) { if self.safe_to_produce(&block) {
let block = self.sign_block(block); if let Some(block) = self.sign_block(block) {
self.beacon_node.publish_beacon_block(block)?; self.beacon_node.publish_beacon_block(block)?;
Ok(PollOutcome::BlockProduced(slot)) Ok(PollOutcome::BlockProduced(slot))
} else {
Ok(PollOutcome::SignerRejection(slot))
}
} else { } else {
Ok(PollOutcome::SlashableBlockNotProduced(slot)) Ok(PollOutcome::SlashableBlockNotProduced(slot))
} }
@ -138,11 +142,30 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
/// ///
/// Important: this function will not check to ensure the block is not slashable. This must be /// Important: this function will not check to ensure the block is not slashable. This must be
/// done upstream. /// done upstream.
fn sign_block(&mut self, block: BeaconBlock) -> BeaconBlock { fn sign_block(&mut self, mut block: BeaconBlock) -> Option<BeaconBlock> {
// TODO: sign the block
// https://github.com/sigp/lighthouse/issues/160
self.store_produce(&block); 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). /// Returns `true` if signing a block is safe (non-slashable).
@ -167,6 +190,11 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
} }
} }
fn hash_tree_root<T>(_input: &T) -> Hash256 {
// TODO: stubbed out.
Hash256::zero()
}
impl From<BeaconNodeError> for Error { impl From<BeaconNodeError> for Error {
fn from(e: BeaconNodeError) -> Error { fn from(e: BeaconNodeError) -> Error {
Error::BeaconNodeError(e) Error::BeaconNodeError(e)
@ -175,13 +203,12 @@ impl From<BeaconNodeError> for Error {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::test_node::TestBeaconNode; use super::test_utils::{TestBeaconNode, TestEpochMap, TestSigner};
use super::*; use super::*;
use slot_clock::TestingSlotClock; use slot_clock::TestingSlotClock;
use std::collections::HashMap;
use types::{ use types::{
test_utils::{SeedableRng, TestRandom, XorShiftRng}, test_utils::{SeedableRng, TestRandom, XorShiftRng},
Signature, Keypair,
}; };
// TODO: implement more thorough testing. // TODO: implement more thorough testing.
@ -189,31 +216,6 @@ mod tests {
// //
// These tests should serve as a good example for future tests. // These tests should serve as a good example for future tests.
type EpochMap = HashMap<u64, u64>;
impl DutiesReader for EpochMap {
fn is_block_production_slot(
&self,
epoch: u64,
slot: u64,
) -> Result<bool, DutiesReaderError> {
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<Signature> {
let mut rng = XorShiftRng::from_seed([42; 16]);
Some(Signature::random_for_test(&mut rng))
}
}
#[test] #[test]
pub fn polling() { pub fn polling() {
let mut rng = XorShiftRng::from_seed([42; 16]); let mut rng = XorShiftRng::from_seed([42; 16]);
@ -221,9 +223,9 @@ mod tests {
let spec = Arc::new(ChainSpec::foundation()); let spec = Arc::new(ChainSpec::foundation());
let slot_clock = Arc::new(RwLock::new(TestingSlotClock::new(0))); let slot_clock = Arc::new(RwLock::new(TestingSlotClock::new(0)));
let beacon_node = Arc::new(TestBeaconNode::default()); 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_slot = 100;
let produce_epoch = produce_slot / spec.epoch_length; let produce_epoch = produce_slot / spec.epoch_length;
epoch_map.insert(produce_epoch, produce_slot); epoch_map.insert(produce_epoch, produce_slot);

View File

@ -1,4 +1,4 @@
use super::traits::{BeaconNode, BeaconNodeError}; use crate::traits::{BeaconNode, BeaconNodeError};
use std::sync::RwLock; use std::sync::RwLock;
use types::BeaconBlock; use types::BeaconBlock;

View File

@ -0,0 +1,14 @@
use crate::{DutiesReader, DutiesReaderError};
use std::collections::HashMap;
pub type TestEpochMap = HashMap<u64, u64>;
impl DutiesReader for TestEpochMap {
fn is_block_production_slot(&self, epoch: u64, slot: u64) -> Result<bool, DutiesReaderError> {
match self.get(&epoch) {
Some(s) if *s == slot => Ok(true),
Some(s) if *s != slot => Ok(false),
_ => Err(DutiesReaderError::UnknownEpoch),
}
}
}

View File

@ -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;

View File

@ -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<bool>,
}
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<Signature> {
Some(Signature::new(message, &self.keypair.sk))
}
}

View File

@ -18,16 +18,18 @@ pub trait BeaconNode: Send + Sync {
fn publish_beacon_block(&self, block: BeaconBlock) -> Result<bool, BeaconNodeError>; fn publish_beacon_block(&self, block: BeaconBlock) -> Result<bool, BeaconNodeError>;
} }
#[derive(Debug, PartialEq, Clone)]
pub enum DutiesReaderError { pub enum DutiesReaderError {
UnknownEpoch, UnknownEpoch,
Poisoned, 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 { pub trait DutiesReader: Send + Sync {
fn is_block_production_slot(&self, epoch: u64, slot: u64) -> Result<bool, DutiesReaderError>; fn is_block_production_slot(&self, epoch: u64, slot: u64) -> Result<bool, DutiesReaderError>;
} }
/// Signs message using an internally-maintained private key.
pub trait Signer { pub trait Signer {
fn bls_sign(message: &[u8]) -> Option<Signature>; fn bls_sign(&self, message: &[u8]) -> Option<Signature>;
} }

View File

@ -1,17 +1,17 @@
use block_producer::{ use block_producer::{
BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, Signer,
}; };
use slog::{error, info, warn, Logger}; use slog::{error, info, warn, Logger};
use slot_clock::SlotClock; use slot_clock::SlotClock;
use std::time::Duration; use std::time::Duration;
pub struct BlockProducerService<T: SlotClock, U: BeaconNode, V: DutiesReader> { pub struct BlockProducerService<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> {
pub block_producer: BlockProducer<T, U, V>, pub block_producer: BlockProducer<T, U, V, W>,
pub poll_interval_millis: u64, pub poll_interval_millis: u64,
pub log: Logger, pub log: Logger,
} }
impl<T: SlotClock, U: BeaconNode, V: DutiesReader> BlockProducerService<T, U, V> { impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducerService<T, U, V, W> {
/// Run a loop which polls the block producer each `poll_interval_millis` millseconds. /// Run a loop which polls the block producer each `poll_interval_millis` millseconds.
/// ///
/// Logs the results of the polls. /// Logs the results of the polls.
@ -39,6 +39,9 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader> BlockProducerService<T, U, V>
Ok(BlockProducerPollOutcome::BeaconNodeUnableToProduceBlock(slot)) => { Ok(BlockProducerPollOutcome::BeaconNodeUnableToProduceBlock(slot)) => {
error!(self.log, "Beacon node unable to produce block"; "slot" => 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)); std::thread::sleep(Duration::from_millis(self.poll_interval_millis));

View File

@ -1,7 +1,7 @@
use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService};
use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap};
use crate::config::ClientConfig; use crate::config::ClientConfig;
use block_producer::BlockProducer; use block_producer::{test_utils::TestSigner, BlockProducer};
use bls::Keypair; use bls::Keypair;
use clap::{App, Arg}; use clap::{App, Arg};
use grpcio::{ChannelBuilder, EnvBuilder}; use grpcio::{ChannelBuilder, EnvBuilder};
@ -139,12 +139,14 @@ fn main() {
// Spawn a new thread to perform block production for the validator. // Spawn a new thread to perform block production for the validator.
let producer_thread = { let producer_thread = {
let spec = spec.clone(); let spec = spec.clone();
let signer = Arc::new(TestSigner::new(keypair.clone()));
let duties_map = duties_map.clone(); let duties_map = duties_map.clone();
let slot_clock = slot_clock.clone(); let slot_clock = slot_clock.clone();
let log = log.clone(); let log = log.clone();
let client = Arc::new(BeaconBlockGrpcClient::new(beacon_block_grpc_client.clone())); let client = Arc::new(BeaconBlockGrpcClient::new(beacon_block_grpc_client.clone()));
thread::spawn(move || { 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 { let mut block_producer_service = BlockProducerService {
block_producer, block_producer,
poll_interval_millis, poll_interval_millis,