Add doc comments for validator service.

This commit is contained in:
Paul Hauner 2019-01-22 12:38:11 +11:00
parent 4fd5424fca
commit 14dfc3223a
No known key found for this signature in database
GPG Key ID: D362883A9218FCC6
12 changed files with 112 additions and 2 deletions

View File

@ -7,6 +7,10 @@ use ssz::{ssz_encode, Decodable};
use types::{BeaconBlock, BeaconBlockBody, Hash256, Signature}; use types::{BeaconBlock, BeaconBlockBody, Hash256, Signature};
impl BeaconNode for BeaconBlockServiceClient { impl BeaconNode for BeaconBlockServiceClient {
/// 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: u64) -> Result<Option<BeaconBlock>, BeaconNodeError> { fn produce_beacon_block(&self, slot: u64) -> Result<Option<BeaconBlock>, BeaconNodeError> {
let mut req = ProduceBeaconBlockRequest::new(); let mut req = ProduceBeaconBlockRequest::new();
req.set_slot(slot); req.set_slot(slot);
@ -42,6 +46,10 @@ impl BeaconNode for BeaconBlockServiceClient {
} }
} }
/// 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<bool, BeaconNodeError> { fn publish_beacon_block(&self, block: BeaconBlock) -> Result<bool, BeaconNodeError> {
let mut req = PublishBeaconBlockRequest::new(); let mut req = PublishBeaconBlockRequest::new();

View File

@ -15,11 +15,17 @@ pub use self::service::BlockProducerService;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum PollOutcome { pub enum PollOutcome {
/// A new block was produced.
BlockProduced(u64), BlockProduced(u64),
/// A block was not produced as it would have been slashable.
SlashableBlockNotProduced(u64), SlashableBlockNotProduced(u64),
/// The validator duties did not require a block to be produced.
BlockProductionNotRequired(u64), BlockProductionNotRequired(u64),
/// The duties for the present epoch were not found.
ProducerDutiesUnknown(u64), ProducerDutiesUnknown(u64),
/// The slot has already been processed, execution was skipped.
SlotAlreadyProcessed(u64), SlotAlreadyProcessed(u64),
/// The Beacon Node was unable to produce a block at that slot.
BeaconNodeUnableToProduceBlock(u64), BeaconNodeUnableToProduceBlock(u64),
} }
@ -33,6 +39,12 @@ pub enum Error {
BeaconNodeError(BeaconNodeError), 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<T: SlotClock, U: BeaconNode> { pub struct BlockProducer<T: SlotClock, U: BeaconNode> {
pub last_processed_slot: u64, pub last_processed_slot: u64,
spec: Arc<ChainSpec>, spec: Arc<ChainSpec>,
@ -42,6 +54,7 @@ pub struct BlockProducer<T: SlotClock, U: BeaconNode> {
} }
impl<T: SlotClock, U: BeaconNode> BlockProducer<T, U> { impl<T: SlotClock, U: BeaconNode> BlockProducer<T, U> {
/// Returns a new instance where `last_processed_slot == 0`.
pub fn new( pub fn new(
spec: Arc<ChainSpec>, spec: Arc<ChainSpec>,
epoch_map: Arc<RwLock<EpochDutiesMap>>, epoch_map: Arc<RwLock<EpochDutiesMap>>,
@ -97,6 +110,16 @@ impl<T: SlotClock, U: BeaconNode> BlockProducer<T, U> {
} }
} }
/// 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<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) {
@ -111,19 +134,36 @@ impl<T: SlotClock, U: BeaconNode> BlockProducer<T, U> {
} }
} }
/// 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 { fn sign_block(&mut self, block: BeaconBlock) -> BeaconBlock {
// TODO: sign the block // TODO: sign the block
// https://github.com/sigp/lighthouse/issues/160
self.store_produce(&block); self.store_produce(&block);
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 { fn safe_to_produce(&self, _block: &BeaconBlock) -> bool {
// TODO: ensure the producer doesn't produce slashable blocks. // TODO: ensure the producer doesn't produce slashable blocks.
// https://github.com/sigp/lighthouse/issues/160
true 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) { fn store_produce(&mut self, _block: &BeaconBlock) {
// TODO: record this block production to prevent future slashings. // TODO: record this block production to prevent future slashings.
// https://github.com/sigp/lighthouse/issues/160
} }
} }
@ -142,6 +182,7 @@ mod tests {
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
// TODO: implement more thorough testing. // TODO: implement more thorough testing.
// https://github.com/sigp/lighthouse/issues/160
// //
// These tests should serve as a good example for future tests. // These tests should serve as a good example for future tests.

View File

@ -10,6 +10,9 @@ pub struct BlockProducerService<T: SlotClock, U: BeaconNode> {
} }
impl<T: SlotClock, U: BeaconNode> BlockProducerService<T, U> { impl<T: SlotClock, U: BeaconNode> BlockProducerService<T, U> {
/// Run a loop which polls the block producer each `poll_interval_millis` millseconds.
///
/// Logs the results of the polls.
pub fn run(&mut self) { pub fn run(&mut self) {
loop { loop {
match self.block_producer.poll() { match self.block_producer.poll() {

View File

@ -5,6 +5,7 @@ use types::BeaconBlock;
type ProduceResult = Result<Option<BeaconBlock>, BeaconNodeError>; type ProduceResult = Result<Option<BeaconBlock>, BeaconNodeError>;
type PublishResult = Result<bool, BeaconNodeError>; type PublishResult = Result<bool, BeaconNodeError>;
/// A test-only struct used to simulate a Beacon Node.
#[derive(Default)] #[derive(Default)]
pub struct TestBeaconNode { pub struct TestBeaconNode {
pub produce_input: RwLock<Option<u64>>, pub produce_input: RwLock<Option<u64>>,
@ -14,16 +15,19 @@ pub struct TestBeaconNode {
} }
impl TestBeaconNode { impl TestBeaconNode {
/// Set the result to be returned when `produce_beacon_block` is called.
pub fn set_next_produce_result(&self, result: ProduceResult) { pub fn set_next_produce_result(&self, result: ProduceResult) {
*self.produce_result.write().unwrap() = Some(result); *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) { pub fn set_next_publish_result(&self, result: PublishResult) {
*self.publish_result.write().unwrap() = Some(result); *self.publish_result.write().unwrap() = Some(result);
} }
} }
impl BeaconNode for TestBeaconNode { impl BeaconNode for TestBeaconNode {
/// Returns the value specified by the `set_next_produce_result`.
fn produce_beacon_block(&self, slot: u64) -> ProduceResult { fn produce_beacon_block(&self, slot: u64) -> ProduceResult {
*self.produce_input.write().unwrap() = Some(slot); *self.produce_input.write().unwrap() = Some(slot);
match *self.produce_result.read().unwrap() { match *self.produce_result.read().unwrap() {
@ -32,6 +36,7 @@ impl BeaconNode for TestBeaconNode {
} }
} }
/// Returns the value specified by the `set_next_publish_result`.
fn publish_beacon_block(&self, block: BeaconBlock) -> PublishResult { fn publish_beacon_block(&self, block: BeaconBlock) -> PublishResult {
*self.publish_input.write().unwrap() = Some(block); *self.publish_input.write().unwrap() = Some(block);
match *self.publish_result.read().unwrap() { match *self.publish_result.read().unwrap() {

View File

@ -6,7 +6,14 @@ pub enum BeaconNodeError {
DecodeFailure, DecodeFailure,
} }
/// Defines the methods required to produce and publish blocks on a Beacon Node.
pub trait BeaconNode: Send + Sync { 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<Option<BeaconBlock>, BeaconNodeError>; fn produce_beacon_block(&self, slot: u64) -> 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<bool, BeaconNodeError>; fn publish_beacon_block(&self, block: BeaconBlock) -> Result<bool, BeaconNodeError>;
} }

View File

@ -11,7 +11,7 @@ pub struct ClientConfig {
const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse-validators"; const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse-validators";
impl ClientConfig { impl ClientConfig {
/// Build a new lighthouse configuration from defaults. /// Build a new configuration from defaults.
pub fn default() -> Self { pub fn default() -> Self {
let data_dir = { let data_dir = {
let home = dirs::home_dir().expect("Unable to determine home dir."); let home = dirs::home_dir().expect("Unable to determine home dir.");

View File

@ -6,6 +6,12 @@ use ssz::ssz_encode;
use types::PublicKey; use types::PublicKey;
impl BeaconNode for ValidatorServiceClient { impl BeaconNode for ValidatorServiceClient {
/// Request the shuffling from the Beacon Node (BN).
///
/// As this function takes a `PublicKey`, it will first attempt to resolve the public key into
/// a validator index, then call the BN for production/attestation duties.
///
/// Note: presently only block production information is returned.
fn request_shuffling( fn request_shuffling(
&self, &self,
epoch: u64, epoch: u64,

View File

@ -13,6 +13,11 @@ use std::sync::{Arc, RwLock};
pub use self::service::DutiesManagerService; pub use self::service::DutiesManagerService;
/// The information required for a validator to propose and attest during some epoch.
///
/// Generally obtained from a Beacon Node, this information contains the validators canonical index
/// (thier sequence in the global validator induction process) and the "shuffling" for that index
/// for some epoch.
#[derive(Debug, PartialEq, Clone, Copy, Default)] #[derive(Debug, PartialEq, Clone, Copy, Default)]
pub struct EpochDuties { pub struct EpochDuties {
pub validator_index: u64, pub validator_index: u64,
@ -21,6 +26,8 @@ pub struct EpochDuties {
} }
impl EpochDuties { impl EpochDuties {
/// Returns `true` if the supplied `slot` is a slot in which the validator should produce a
/// block.
pub fn is_block_production_slot(&self, slot: u64) -> bool { pub fn is_block_production_slot(&self, slot: u64) -> bool {
match self.block_production_slot { match self.block_production_slot {
Some(s) if s == slot => true, Some(s) if s == slot => true,
@ -29,13 +36,20 @@ impl EpochDuties {
} }
} }
/// Maps an `epoch` to some `EpochDuties` for a single validator.
pub type EpochDutiesMap = HashMap<u64, EpochDuties>; pub type EpochDutiesMap = HashMap<u64, EpochDuties>;
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
pub enum PollOutcome { pub enum PollOutcome {
/// The `EpochDuties` were not updated during this poll.
NoChange(u64), NoChange(u64),
/// The `EpochDuties` for the `epoch` were previously unknown, but obtained in the poll.
NewDuties(u64, EpochDuties), NewDuties(u64, EpochDuties),
/// New `EpochDuties` were obtained, different to those which were previously known. This is
/// likely to be the result of chain re-organisation.
DutiesChanged(u64, EpochDuties), DutiesChanged(u64, EpochDuties),
/// The Beacon Node was unable to return the duties as the validator is unknown, or the
/// shuffling for the epoch is unknown.
UnknownValidatorOrEpoch(u64), UnknownValidatorOrEpoch(u64),
} }
@ -49,8 +63,13 @@ pub enum Error {
BeaconNodeError(BeaconNodeError), BeaconNodeError(BeaconNodeError),
} }
/// A polling state machine which ensures the latest `EpochDuties` are obtained from the Beacon
/// Node.
///
/// There is a single `DutiesManager` per validator instance.
pub struct DutiesManager<T: SlotClock, U: BeaconNode> { pub struct DutiesManager<T: SlotClock, U: BeaconNode> {
pub duties_map: Arc<RwLock<EpochDutiesMap>>, pub duties_map: Arc<RwLock<EpochDutiesMap>>,
/// The validator's public key.
pub pubkey: PublicKey, pub pubkey: PublicKey,
pub spec: Arc<ChainSpec>, pub spec: Arc<ChainSpec>,
pub slot_clock: Arc<RwLock<T>>, pub slot_clock: Arc<RwLock<T>>,
@ -58,6 +77,10 @@ pub struct DutiesManager<T: SlotClock, U: BeaconNode> {
} }
impl<T: SlotClock, U: BeaconNode> DutiesManager<T, U> { impl<T: SlotClock, U: BeaconNode> DutiesManager<T, U> {
/// Poll the Beacon Node for `EpochDuties`.
///
/// The present `epoch` will be learned from the supplied `SlotClock`. In production this will
/// be a wall-clock (e.g., system time, remote server time, etc.).
pub fn poll(&self) -> Result<PollOutcome, Error> { pub fn poll(&self) -> Result<PollOutcome, Error> {
let slot = self let slot = self
.slot_clock .slot_clock
@ -109,6 +132,7 @@ mod tests {
use slot_clock::TestingSlotClock; use slot_clock::TestingSlotClock;
// TODO: implement more thorough testing. // TODO: implement more thorough testing.
// https://github.com/sigp/lighthouse/issues/160
// //
// These tests should serve as a good example for future tests. // These tests should serve as a good example for future tests.

View File

@ -11,6 +11,9 @@ pub struct DutiesManagerService<T: SlotClock, U: BeaconNode> {
} }
impl<T: SlotClock, U: BeaconNode> DutiesManagerService<T, U> { impl<T: SlotClock, U: BeaconNode> DutiesManagerService<T, U> {
/// Run a loop which polls the manager each `poll_interval_millis` milliseconds.
///
/// Logs the results of the polls.
pub fn run(&mut self) { pub fn run(&mut self) {
loop { loop {
match self.manager.poll() { match self.manager.poll() {

View File

@ -5,6 +5,7 @@ use std::sync::RwLock;
type ShufflingResult = Result<Option<EpochDuties>, BeaconNodeError>; type ShufflingResult = Result<Option<EpochDuties>, BeaconNodeError>;
/// A test-only struct used to simulate a Beacon Node.
#[derive(Default)] #[derive(Default)]
pub struct TestBeaconNode { pub struct TestBeaconNode {
pub request_shuffling_input: RwLock<Option<(u64, PublicKey)>>, pub request_shuffling_input: RwLock<Option<(u64, PublicKey)>>,
@ -12,12 +13,14 @@ pub struct TestBeaconNode {
} }
impl TestBeaconNode { impl TestBeaconNode {
/// Set the result to be returned when `request_shuffling` is called.
pub fn set_next_shuffling_result(&self, result: ShufflingResult) { pub fn set_next_shuffling_result(&self, result: ShufflingResult) {
*self.request_shuffling_result.write().unwrap() = Some(result); *self.request_shuffling_result.write().unwrap() = Some(result);
} }
} }
impl BeaconNode for TestBeaconNode { impl BeaconNode for TestBeaconNode {
/// Returns the value specified by the `set_next_shuffling_result`.
fn request_shuffling(&self, epoch: u64, public_key: &PublicKey) -> ShufflingResult { fn request_shuffling(&self, epoch: u64, public_key: &PublicKey) -> ShufflingResult {
*self.request_shuffling_input.write().unwrap() = Some((epoch, public_key.clone())); *self.request_shuffling_input.write().unwrap() = Some((epoch, public_key.clone()));
match *self.request_shuffling_result.read().unwrap() { match *self.request_shuffling_result.read().unwrap() {

View File

@ -6,7 +6,11 @@ pub enum BeaconNodeError {
RemoteFailure(String), RemoteFailure(String),
} }
/// Defines the methods required to obtain a validators shuffling from a Beacon Node.
pub trait BeaconNode: Send + Sync { pub trait BeaconNode: Send + Sync {
/// Get the shuffling for the given epoch and public key.
///
/// Returns Ok(None) if the public key is unknown, or the shuffling for that epoch is unknown.
fn request_shuffling( fn request_shuffling(
&self, &self,
epoch: u64, epoch: u64,

View File

@ -83,6 +83,7 @@ fn main() {
// Ethereum // Ethereum
// //
// TODO: Permit loading a custom spec from file. // TODO: Permit loading a custom spec from file.
// https://github.com/sigp/lighthouse/issues/160
let spec = Arc::new(ChainSpec::foundation()); let spec = Arc::new(ChainSpec::foundation());
// Clock for determining the present slot. // Clock for determining the present slot.
@ -99,13 +100,16 @@ fn main() {
/* /*
* Start threads. * Start threads.
*/ */
let keypairs = vec![Keypair::random()];
let mut threads = vec![]; let mut threads = vec![];
// TODO: keypairs are randomly generated; they should be loaded from a file or generated.
// https://github.com/sigp/lighthouse/issues/160
let keypairs = vec![Keypair::random()];
for keypair in keypairs { for keypair in keypairs {
info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id());
let duties_map = Arc::new(RwLock::new(EpochDutiesMap::new())); let duties_map = Arc::new(RwLock::new(EpochDutiesMap::new()));
// Spawn a new thread to maintain the validator's `EpochDuties`.
let duties_manager_thread = { let duties_manager_thread = {
let spec = spec.clone(); let spec = spec.clone();
let duties_map = duties_map.clone(); let duties_map = duties_map.clone();
@ -131,6 +135,7 @@ fn main() {
}) })
}; };
// 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 duties_map = duties_map.clone(); let duties_map = duties_map.clone();
@ -152,6 +157,7 @@ fn main() {
threads.push((duties_manager_thread, producer_thread)); threads.push((duties_manager_thread, producer_thread));
} }
// Naively wait for all the threads to complete.
for tuple in threads { for tuple in threads {
let (manager, producer) = tuple; let (manager, producer) = tuple;
let _ = producer.join(); let _ = producer.join();