diff --git a/validator_client/src/block_producer/grpc.rs b/validator_client/src/block_producer/grpc.rs index 4032d49f3..f3156ab9f 100644 --- a/validator_client/src/block_producer/grpc.rs +++ b/validator_client/src/block_producer/grpc.rs @@ -7,6 +7,10 @@ use ssz::{ssz_encode, Decodable}; use types::{BeaconBlock, BeaconBlockBody, Hash256, Signature}; 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, BeaconNodeError> { let mut req = ProduceBeaconBlockRequest::new(); 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 { let mut req = PublishBeaconBlockRequest::new(); diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index aa4c088f3..f3cd0199b 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -15,11 +15,17 @@ pub use self::service::BlockProducerService; #[derive(Debug, PartialEq)] pub enum PollOutcome { + /// A new block was produced. BlockProduced(u64), + /// A block was not produced as it would have been slashable. SlashableBlockNotProduced(u64), + /// The validator duties did not require a block to be produced. BlockProductionNotRequired(u64), + /// The duties for the present epoch were not found. ProducerDutiesUnknown(u64), + /// The slot has already been processed, execution was skipped. SlotAlreadyProcessed(u64), + /// The Beacon Node was unable to produce a block at that slot. BeaconNodeUnableToProduceBlock(u64), } @@ -33,6 +39,12 @@ pub enum Error { 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 { pub last_processed_slot: u64, spec: Arc, @@ -42,6 +54,7 @@ pub struct BlockProducer { } impl BlockProducer { + /// Returns a new instance where `last_processed_slot == 0`. pub fn new( spec: Arc, epoch_map: Arc>, @@ -97,6 +110,16 @@ 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, slot: u64) -> Result { if let Some(block) = self.beacon_node.produce_beacon_block(slot)? { if self.safe_to_produce(&block) { @@ -111,19 +134,36 @@ impl BlockProducer { } } + /// 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 { // TODO: sign the block + // https://github.com/sigp/lighthouse/issues/160 self.store_produce(&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 { // 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 } } @@ -142,6 +182,7 @@ mod tests { use types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; // TODO: implement more thorough testing. + // https://github.com/sigp/lighthouse/issues/160 // // These tests should serve as a good example for future tests. diff --git a/validator_client/src/block_producer/service.rs b/validator_client/src/block_producer/service.rs index 222e54d47..ffdb33029 100644 --- a/validator_client/src/block_producer/service.rs +++ b/validator_client/src/block_producer/service.rs @@ -10,6 +10,9 @@ pub struct BlockProducerService { } impl BlockProducerService { + /// Run a loop which polls the block producer each `poll_interval_millis` millseconds. + /// + /// Logs the results of the polls. pub fn run(&mut self) { loop { match self.block_producer.poll() { diff --git a/validator_client/src/block_producer/test_node.rs b/validator_client/src/block_producer/test_node.rs index 36d47e3c3..e99613e8f 100644 --- a/validator_client/src/block_producer/test_node.rs +++ b/validator_client/src/block_producer/test_node.rs @@ -5,6 +5,7 @@ use types::BeaconBlock; type ProduceResult = Result, BeaconNodeError>; type PublishResult = Result; +/// A test-only struct used to simulate a Beacon Node. #[derive(Default)] pub struct TestBeaconNode { pub produce_input: RwLock>, @@ -14,16 +15,19 @@ pub struct TestBeaconNode { } impl TestBeaconNode { + /// Set the result to be returned when `produce_beacon_block` is called. pub fn set_next_produce_result(&self, result: ProduceResult) { *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) { *self.publish_result.write().unwrap() = Some(result); } } impl BeaconNode for TestBeaconNode { + /// Returns the value specified by the `set_next_produce_result`. fn produce_beacon_block(&self, slot: u64) -> ProduceResult { *self.produce_input.write().unwrap() = Some(slot); 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 { *self.publish_input.write().unwrap() = Some(block); match *self.publish_result.read().unwrap() { diff --git a/validator_client/src/block_producer/traits.rs b/validator_client/src/block_producer/traits.rs index aaae031ec..be1c73bda 100644 --- a/validator_client/src/block_producer/traits.rs +++ b/validator_client/src/block_producer/traits.rs @@ -6,7 +6,14 @@ pub enum BeaconNodeError { DecodeFailure, } +/// Defines the methods required to produce and publish blocks on a 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: u64) -> Result, BeaconNodeError>; + /// Request that the node publishes a block. + /// + /// Returns `true` if the publish was sucessful. fn publish_beacon_block(&self, block: BeaconBlock) -> Result; } diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 4b646074d..104a4bbe6 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -11,7 +11,7 @@ pub struct ClientConfig { const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse-validators"; impl ClientConfig { - /// Build a new lighthouse configuration from defaults. + /// Build a new configuration from defaults. pub fn default() -> Self { let data_dir = { let home = dirs::home_dir().expect("Unable to determine home dir."); diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index 7b4e1e617..b4a2fac51 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -6,6 +6,12 @@ use ssz::ssz_encode; use types::PublicKey; 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( &self, epoch: u64, diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index e8438b3db..4656715ba 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -13,6 +13,11 @@ use std::sync::{Arc, RwLock}; 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)] pub struct EpochDuties { pub validator_index: u64, @@ -21,6 +26,8 @@ pub struct 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 { match self.block_production_slot { 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; #[derive(Debug, PartialEq, Clone, Copy)] pub enum PollOutcome { + /// The `EpochDuties` were not updated during this poll. NoChange(u64), + /// The `EpochDuties` for the `epoch` were previously unknown, but obtained in the poll. 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), + /// The Beacon Node was unable to return the duties as the validator is unknown, or the + /// shuffling for the epoch is unknown. UnknownValidatorOrEpoch(u64), } @@ -49,8 +63,13 @@ pub enum Error { 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 { pub duties_map: Arc>, + /// The validator's public key. pub pubkey: PublicKey, pub spec: Arc, pub slot_clock: Arc>, @@ -58,6 +77,10 @@ pub struct DutiesManager { } impl DutiesManager { + /// 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 { let slot = self .slot_clock @@ -109,6 +132,7 @@ mod tests { use slot_clock::TestingSlotClock; // TODO: implement more thorough testing. + // https://github.com/sigp/lighthouse/issues/160 // // These tests should serve as a good example for future tests. diff --git a/validator_client/src/duties/service.rs b/validator_client/src/duties/service.rs index 401b7dddd..bdb6faefa 100644 --- a/validator_client/src/duties/service.rs +++ b/validator_client/src/duties/service.rs @@ -11,6 +11,9 @@ pub struct DutiesManagerService { } impl DutiesManagerService { + /// Run a loop which polls the manager each `poll_interval_millis` milliseconds. + /// + /// Logs the results of the polls. pub fn run(&mut self) { loop { match self.manager.poll() { diff --git a/validator_client/src/duties/test_node.rs b/validator_client/src/duties/test_node.rs index 4ec002224..2b1d65172 100644 --- a/validator_client/src/duties/test_node.rs +++ b/validator_client/src/duties/test_node.rs @@ -5,6 +5,7 @@ use std::sync::RwLock; type ShufflingResult = Result, BeaconNodeError>; +/// A test-only struct used to simulate a Beacon Node. #[derive(Default)] pub struct TestBeaconNode { pub request_shuffling_input: RwLock>, @@ -12,12 +13,14 @@ pub struct TestBeaconNode { } impl TestBeaconNode { + /// Set the result to be returned when `request_shuffling` is called. pub fn set_next_shuffling_result(&self, result: ShufflingResult) { *self.request_shuffling_result.write().unwrap() = Some(result); } } impl BeaconNode for TestBeaconNode { + /// Returns the value specified by the `set_next_shuffling_result`. fn request_shuffling(&self, epoch: u64, public_key: &PublicKey) -> ShufflingResult { *self.request_shuffling_input.write().unwrap() = Some((epoch, public_key.clone())); match *self.request_shuffling_result.read().unwrap() { diff --git a/validator_client/src/duties/traits.rs b/validator_client/src/duties/traits.rs index 14c2adf95..38d61f967 100644 --- a/validator_client/src/duties/traits.rs +++ b/validator_client/src/duties/traits.rs @@ -6,7 +6,11 @@ pub enum BeaconNodeError { RemoteFailure(String), } +/// Defines the methods required to obtain a validators shuffling from a Beacon Node. 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( &self, epoch: u64, diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index a52ffe124..bbbc0b4c3 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -83,6 +83,7 @@ fn main() { // Ethereum // // TODO: Permit loading a custom spec from file. + // https://github.com/sigp/lighthouse/issues/160 let spec = Arc::new(ChainSpec::foundation()); // Clock for determining the present slot. @@ -99,13 +100,16 @@ fn main() { /* * Start threads. */ - let keypairs = vec![Keypair::random()]; 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 { info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); let duties_map = Arc::new(RwLock::new(EpochDutiesMap::new())); + // Spawn a new thread to maintain the validator's `EpochDuties`. let duties_manager_thread = { let spec = spec.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 spec = spec.clone(); let duties_map = duties_map.clone(); @@ -152,6 +157,7 @@ fn main() { threads.push((duties_manager_thread, producer_thread)); } + // Naively wait for all the threads to complete. for tuple in threads { let (manager, producer) = tuple; let _ = producer.join();