Implement block producer for validator client

This commit is contained in:
Age Manning 2019-03-29 16:33:27 +11:00
parent f8201edddd
commit eea772de3e
No known key found for this signature in database
GPG Key ID: 05EED64B79E06A93
10 changed files with 92 additions and 78 deletions

View File

@ -19,7 +19,9 @@ service BeaconNodeService {
/// Service that handles block production /// Service that handles block production
service BeaconBlockService { service BeaconBlockService {
// Requests a block to be signed from the beacon node.
rpc ProduceBeaconBlock(ProduceBeaconBlockRequest) returns (ProduceBeaconBlockResponse); rpc ProduceBeaconBlock(ProduceBeaconBlockRequest) returns (ProduceBeaconBlockResponse);
// Responds to the node the signed block to be published.
rpc PublishBeaconBlock(PublishBeaconBlockRequest) returns (PublishBeaconBlockResponse); rpc PublishBeaconBlock(PublishBeaconBlockRequest) returns (PublishBeaconBlockResponse);
} }
@ -64,6 +66,7 @@ message Empty {}
// Validator requests an unsigned proposal. // Validator requests an unsigned proposal.
message ProduceBeaconBlockRequest { message ProduceBeaconBlockRequest {
uint64 slot = 1; uint64 slot = 1;
bytes randao_reveal = 2;
} }
// Beacon node returns an unsigned proposal. // Beacon node returns an unsigned proposal.

View File

@ -8,11 +8,6 @@ edition = "2018"
name = "validator_client" name = "validator_client"
path = "src/main.rs" path = "src/main.rs"
[lib]
name = "validator_client"
path = "src/lib.rs"
[dependencies] [dependencies]
block_proposer = { path = "../eth2/block_proposer" } block_proposer = { path = "../eth2/block_proposer" }
attester = { path = "../eth2/attester" } attester = { path = "../eth2/attester" }

View File

@ -1,23 +0,0 @@
#[Derive(Debug, PartialEq, Clone)]
pub enum BeaconNodeError {
RemoteFailure(String),
DecodeFailure,
}
/// Defines the methods required to produce and publish blocks on a Beacon Node. Abstracts the
/// actual 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: Slot,
randao_reveal: &Signature,
) -> Result<Option<BeaconBlock>, BeaconNodeError>;
/// Request that the node publishes a block.
///
/// Returns `true` if the publish was sucessful.
fn publish_beacon_block(&self, block: BeaconBlock) -> Result<PublishOutcome, BeaconNodeError>;
}

View File

@ -0,0 +1,33 @@
use types::{BeaconBlock, Signature, Slot};
#[derive(Debug, PartialEq, Clone)]
pub enum BeaconBlockNodeError {
RemoteFailure(String),
DecodeFailure,
}
#[derive(Debug, PartialEq, Clone)]
pub enum PublishOutcome {
ValidBlock,
InvalidBlock(String),
}
/// Defines the methods required to produce and publish blocks on a Beacon Node. Abstracts the
/// actual beacon node.
pub trait BeaconBlockNode: 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: Slot,
randao_reveal: &Signature,
) -> Result<Option<BeaconBlock>, BeaconBlockNodeError>;
/// Request that the node publishes a block.
///
/// Returns `true` if the publish was successful.
fn publish_beacon_block(
&self,
block: BeaconBlock,
) -> Result<PublishOutcome, BeaconBlockNodeError>;
}

View File

@ -1,10 +1,8 @@
pub mod test_utils; use super::beacon_block_node::{BeaconBlockNode, BeaconBlockNodeError};
mod traits; use crate::signer::Signer;
use slot_clock::SlotClock;
use ssz::{SignedRoot, TreeHash}; use ssz::{SignedRoot, TreeHash};
use std::sync::Arc; use std::sync::Arc;
use types::{BeaconBlock, ChainSpec, Domain, Slot}; use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Error { pub enum Error {
@ -13,11 +11,11 @@ pub enum Error {
EpochMapPoisoned, EpochMapPoisoned,
SlotClockPoisoned, SlotClockPoisoned,
EpochLengthIsZero, EpochLengthIsZero,
BeaconNodeError(BeaconNodeError), BeaconBlockNodeError(BeaconBlockNodeError),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum BlockProducerEvent { pub enum ValidatorEvent {
/// A new block was produced. /// A new block was produced.
BlockProduced(Slot), BlockProduced(Slot),
/// A block was not produced as it would have been slashable. /// A block was not produced as it would have been slashable.
@ -32,21 +30,20 @@ pub enum BlockProducerEvent {
/// This struct contains the logic for requesting and signing beacon blocks for a validator. The /// This struct contains the logic for requesting and signing beacon blocks for a validator. The
/// validator can abstractly sign via the Signer trait object. /// validator can abstractly sign via the Signer trait object.
pub struct BlockProducer<B: BeaconNode, S: Signer> { pub struct BlockProducer<B: BeaconBlockNode, S: Signer> {
/// The current fork. /// The current fork.
pub fork: Fork, pub fork: Fork,
/// The current slot to produce a block for. /// The current slot to produce a block for.
pub slot: Slot, pub slot: Slot,
/// The current epoch. /// The current epoch.
pub epoch: Epoch, pub spec: Arc<ChainSpec>,
/// The beacon node to connect to. /// The beacon node to connect to.
pub beacon_node: Arc<B>, pub beacon_node: Arc<B>,
/// The signer to sign the block. /// The signer to sign the block.
pub signer: Arc<S>, pub signer: Arc<S>,
} }
impl<B: BeaconNode, S: Signer> BlockProducer<B, S> { impl<B: BeaconBlockNode, S: Signer> BlockProducer<B, S> {
/// Produce a block at some slot. /// Produce a block at some slot.
/// ///
/// Assumes that a block is required at this slot (does not check the duties). /// Assumes that a block is required at this slot (does not check the duties).
@ -57,43 +54,38 @@ impl<B: BeaconNode, S: Signer> BlockProducer<B, S> {
/// ///
/// The slash-protection code is not yet implemented. There is zero protection against /// The slash-protection code is not yet implemented. There is zero protection against
/// slashing. /// slashing.
fn produce_block(&mut self) -> Result<BlockProducerEvent, Error> { fn produce_block(&mut self) -> Result<ValidatorEvent, Error> {
let epoch = self.slot.epoch(self.spec.slots_per_epoch);
let randao_reveal = { let randao_reveal = {
// TODO: add domain, etc to this message. Also ensure result matches `into_to_bytes32`. let message = epoch.hash_tree_root();
let message = slot.epoch(self.spec.slots_per_epoch).hash_tree_root(); let randao_reveal = match self.signer.sign_randao_reveal(
match self.signer.sign_randao_reveal(
&message, &message,
self.spec self.spec.get_domain(epoch, Domain::Randao, &self.fork),
.get_domain(slot.epoch(self.spec.slots_per_epoch), Domain::Randao, &fork),
) { ) {
None => return Ok(PollOutcome::SignerRejection(slot)), None => return Ok(ValidatorEvent::SignerRejection(self.slot)),
Some(signature) => signature, Some(signature) => signature,
} };
randao_reveal
}; };
if let Some(block) = self if let Some(block) = self
.beacon_node .beacon_node
.produce_beacon_block(slot, &randao_reveal)? .produce_beacon_block(self.slot, &randao_reveal)?
{ {
if self.safe_to_produce(&block) { if self.safe_to_produce(&block) {
let domain = self.spec.get_domain( let domain = self.spec.get_domain(epoch, Domain::BeaconBlock, &self.fork);
slot.epoch(self.spec.slots_per_epoch),
Domain::BeaconBlock,
&fork,
);
if let Some(block) = self.sign_block(block, domain) { if let Some(block) = self.sign_block(block, domain) {
self.beacon_node.publish_beacon_block(block)?; self.beacon_node.publish_beacon_block(block)?;
Ok(PollOutcome::BlockProduced(slot)) Ok(ValidatorEvent::BlockProduced(self.slot))
} else { } else {
Ok(PollOutcome::SignerRejection(slot)) Ok(ValidatorEvent::SignerRejection(self.slot))
} }
} else { } else {
Ok(PollOutcome::SlashableBlockNotProduced(slot)) Ok(ValidatorEvent::SlashableBlockNotProduced(self.slot))
} }
} else { } else {
Ok(PollOutcome::BeaconNodeUnableToProduceBlock(slot)) Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(self.slot))
} }
} }
@ -138,13 +130,12 @@ impl<B: BeaconNode, S: Signer> BlockProducer<B, S> {
} }
} }
impl From<BeaconNodeError> for Error { impl From<BeaconBlockNodeError> for Error {
fn from(e: BeaconNodeError) -> Error { fn from(e: BeaconBlockNodeError) -> Error {
Error::BeaconNodeError(e) Error::BeaconBlockNodeError(e)
} }
} }
/* Old tests - Re-work for new logic /* Old tests - Re-work for new logic
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -225,3 +216,4 @@ mod tests {
); );
} }
} }
*/

View File

@ -1,3 +1,7 @@
mod beacon_block_node;
mod block_producer;
use self::beacon_block_node::*;
use protos::services::{ use protos::services::{
BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest,
}; };
@ -9,16 +13,16 @@ use types::{BeaconBlock, Signature, Slot};
/// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be /// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be
/// implemented upon it. /// implemented upon it.
pub struct BeaconBlockGrpcClient { pub struct BeaconBlockGrpcClient {
inner: Arc<BeaconBlockServiceClient>, client: Arc<BeaconBlockServiceClient>,
} }
impl BeaconBlockGrpcClient { impl BeaconBlockGrpcClient {
pub fn new(client: Arc<BeaconBlockServiceClient>) -> Self { pub fn new(client: Arc<BeaconBlockServiceClient>) -> Self {
Self { inner: client } Self { client }
} }
} }
impl BeaconNode for BeaconBlockGrpcClient { impl BeaconBlockNode for BeaconBlockGrpcClient {
/// Request a Beacon Node (BN) to produce a new block at the supplied slot. /// 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 /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the
@ -26,23 +30,26 @@ impl BeaconNode for BeaconBlockGrpcClient {
fn produce_beacon_block( fn produce_beacon_block(
&self, &self,
slot: Slot, slot: Slot,
// TODO: use randao_reveal, when proto APIs have been updated. randao_reveal: &Signature,
_randao_reveal: &Signature, ) -> Result<Option<BeaconBlock>, BeaconBlockNodeError> {
) -> Result<Option<BeaconBlock>, BeaconNodeError> { // request a beacon block from the node
let mut req = ProduceBeaconBlockRequest::new(); let mut req = ProduceBeaconBlockRequest::new();
req.set_slot(slot.as_u64()); 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 let reply = self
.client .client
.produce_beacon_block(&req) .produce_beacon_block(&req)
.map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?;
// format the reply
if reply.has_block() { if reply.has_block() {
let block = reply.get_block(); let block = reply.get_block();
let ssz = block.get_ssz(); let ssz = block.get_ssz();
let (block, _i) = let (block, _i) = BeaconBlock::ssz_decode(&ssz, 0)
BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| BeaconNodeError::DecodeFailure)?; .map_err(|_| BeaconBlockNodeError::DecodeFailure)?;
Ok(Some(block)) Ok(Some(block))
} else { } else {
@ -54,12 +61,14 @@ impl BeaconNode for BeaconBlockGrpcClient {
/// ///
/// Generally, this will be called after a `produce_beacon_block` call with a block that has /// Generally, this will be called after a `produce_beacon_block` call with a block that has
/// been completed (signed) by the validator client. /// been completed (signed) by the validator client.
fn publish_beacon_block(&self, block: BeaconBlock) -> Result<PublishOutcome, BeaconNodeError> { fn publish_beacon_block(
&self,
block: BeaconBlock,
) -> Result<PublishOutcome, BeaconBlockNodeError> {
let mut req = PublishBeaconBlockRequest::new(); let mut req = PublishBeaconBlockRequest::new();
let ssz = ssz_encode(&block); let ssz = ssz_encode(&block);
// TODO: this conversion is incomplete; fix it.
let mut grpc_block = GrpcBeaconBlock::new(); let mut grpc_block = GrpcBeaconBlock::new();
grpc_block.set_ssz(ssz); grpc_block.set_ssz(ssz);
@ -68,7 +77,7 @@ impl BeaconNode for BeaconBlockGrpcClient {
let reply = self let reply = self
.client .client
.publish_beacon_block(&req) .publish_beacon_block(&req)
.map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?;
if reply.get_success() { if reply.get_success() {
Ok(PublishOutcome::ValidBlock) Ok(PublishOutcome::ValidBlock)

View File

@ -1,3 +0,0 @@
pub mod config;
pub use crate::config::Config;

View File

@ -4,6 +4,7 @@ mod config;
mod duties; mod duties;
pub mod error; pub mod error;
mod service; mod service;
mod signer;
use crate::config::Config as ValidatorClientConfig; use crate::config::Config as ValidatorClientConfig;
use clap::{App, Arg}; use clap::{App, Arg};

View File

@ -9,7 +9,7 @@
/// data from the beacon node and performs the signing before publishing the block to the beacon /// data from the beacon node and performs the signing before publishing the block to the beacon
/// node. /// node.
use crate::attester_service::{AttestationGrpcClient, AttesterService}; use crate::attester_service::{AttestationGrpcClient, AttesterService};
use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use crate::block_producer_service::BeaconBlockGrpcClient;
use crate::config::Config as ValidatorConfig; use crate::config::Config as ValidatorConfig;
use crate::duties::UpdateOutcome; use crate::duties::UpdateOutcome;
use crate::duties::{DutiesManager, EpochDutiesMap}; use crate::duties::{DutiesManager, EpochDutiesMap};

View File

@ -0,0 +1,7 @@
use types::Signature;
/// Signs message using an internally-maintained private key.
pub trait Signer {
fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option<Signature>;
fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option<Signature>;
}