Restructure of validator client service and block producer

This commit is contained in:
Age Manning 2019-03-29 17:28:07 +11:00
parent eea772de3e
commit 9cdb7bb716
No known key found for this signature in database
GPG Key ID: 05EED64B79E06A93
8 changed files with 131 additions and 152 deletions

View File

@ -2,12 +2,12 @@ use super::EpochDuties;
use types::{Epoch, PublicKey}; use types::{Epoch, PublicKey};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum BeaconNodeError { pub enum BeaconNodeDutiesError {
RemoteFailure(String), RemoteFailure(String),
} }
/// Defines the methods required to obtain a validators shuffling from a Beacon Node. /// Defines the methods required to obtain a validators shuffling from a Beacon Node.
pub trait BeaconNode: Send + Sync { pub trait BeaconNodeDuties: Send + Sync {
/// Gets the duties for all validators. /// Gets the duties for all validators.
/// ///
/// Returns a vector of EpochDuties for each validator public key. The entry will be None for /// Returns a vector of EpochDuties for each validator public key. The entry will be None for
@ -16,5 +16,5 @@ pub trait BeaconNode: Send + Sync {
&self, &self,
epoch: Epoch, epoch: Epoch,
pubkeys: &[PublicKey], pubkeys: &[PublicKey],
) -> Result<EpochDuties, BeaconNodeError>; ) -> Result<EpochDuties, BeaconNodeDutiesError>;
} }

View File

@ -1,5 +1,5 @@
use super::beacon_node_duties::{BeaconNodeDuties, BeaconNodeDutiesError};
use super::epoch_duties::{EpochDuties, EpochDuty}; use super::epoch_duties::{EpochDuties, EpochDuty};
use super::traits::{BeaconNode, BeaconNodeError};
use grpcio::CallOption; use grpcio::CallOption;
use protos::services::{GetDutiesRequest, Validators}; use protos::services::{GetDutiesRequest, Validators};
use protos::services_grpc::ValidatorServiceClient; use protos::services_grpc::ValidatorServiceClient;
@ -8,13 +8,13 @@ use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use types::{Epoch, PublicKey, Slot}; use types::{Epoch, PublicKey, Slot};
impl BeaconNode for ValidatorServiceClient { impl BeaconNodeDuties for ValidatorServiceClient {
/// Requests all duties (block signing and committee attesting) from the Beacon Node (BN). /// Requests all duties (block signing and committee attesting) from the Beacon Node (BN).
fn request_duties( fn request_duties(
&self, &self,
epoch: Epoch, epoch: Epoch,
pubkeys: &[PublicKey], pubkeys: &[PublicKey],
) -> Result<EpochDuties, BeaconNodeError> { ) -> Result<EpochDuties, BeaconNodeDutiesError> {
// Get the required duties from all validators // Get the required duties from all validators
// build the request // build the request
let mut req = GetDutiesRequest::new(); let mut req = GetDutiesRequest::new();
@ -29,7 +29,7 @@ impl BeaconNode for ValidatorServiceClient {
// send the request, get the duties reply // send the request, get the duties reply
let reply = self let reply = self
.get_validator_duties(&req) .get_validator_duties(&req)
.map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; .map_err(|err| BeaconNodeDutiesError::RemoteFailure(format!("{:?}", err)))?;
let mut epoch_duties: HashMap<PublicKey, Option<EpochDuty>> = HashMap::new(); let mut epoch_duties: HashMap<PublicKey, Option<EpochDuty>> = HashMap::new();
for (index, validator_duty) in reply.get_active_validators().iter().enumerate() { for (index, validator_duty) in reply.get_active_validators().iter().enumerate() {

View File

@ -1,13 +1,13 @@
mod beacon_node_duties;
mod epoch_duties; mod epoch_duties;
mod grpc; mod grpc;
// TODO: reintroduce tests // TODO: reintroduce tests
//#[cfg(test)] //#[cfg(test)]
//mod test_node; //mod test_node;
mod traits;
pub use self::beacon_node_duties::{BeaconNodeDuties, BeaconNodeDutiesError};
use self::epoch_duties::{EpochDuties, EpochDutiesMapError}; use self::epoch_duties::{EpochDuties, EpochDutiesMapError};
pub use self::epoch_duties::{EpochDutiesMap, WorkInfo}; pub use self::epoch_duties::{EpochDutiesMap, WorkInfo};
use self::traits::{BeaconNode, BeaconNodeError};
use futures::Async; use futures::Async;
use slog::{debug, error, info}; use slog::{debug, error, info};
use std::sync::Arc; use std::sync::Arc;
@ -29,7 +29,7 @@ pub enum UpdateOutcome {
pub enum Error { pub enum Error {
DutiesMapPoisoned, DutiesMapPoisoned,
EpochMapPoisoned, EpochMapPoisoned,
BeaconNodeError(BeaconNodeError), BeaconNodeDutiesError(BeaconNodeDutiesError),
UnknownEpoch, UnknownEpoch,
UnknownValidator, UnknownValidator,
} }
@ -38,14 +38,14 @@ pub enum Error {
/// Node. /// Node.
/// ///
/// This keeps track of all validator keys and required voting slots. /// This keeps track of all validator keys and required voting slots.
pub struct DutiesManager<U: BeaconNode> { pub struct DutiesManager<U: BeaconNodeDuties> {
pub duties_map: RwLock<EpochDutiesMap>, pub duties_map: RwLock<EpochDutiesMap>,
/// A list of all public keys known to the validator service. /// A list of all public keys known to the validator service.
pub pubkeys: Vec<PublicKey>, pub pubkeys: Vec<PublicKey>,
pub beacon_node: Arc<U>, pub beacon_node: Arc<U>,
} }
impl<U: BeaconNode> DutiesManager<U> { impl<U: BeaconNodeDuties> DutiesManager<U> {
/// Check the Beacon Node for `EpochDuties`. /// Check the Beacon Node for `EpochDuties`.
/// ///
/// be a wall-clock (e.g., system time, remote server time, etc.). /// be a wall-clock (e.g., system time, remote server time, etc.).
@ -112,9 +112,9 @@ impl<U: BeaconNode> DutiesManager<U> {
} }
//TODO: Use error_chain to handle errors //TODO: Use error_chain to handle errors
impl From<BeaconNodeError> for Error { impl From<BeaconNodeDutiesError> for Error {
fn from(e: BeaconNodeError) -> Error { fn from(e: BeaconNodeDutiesError) -> Error {
Error::BeaconNodeError(e) Error::BeaconNodeDutiesError(e)
} }
} }

View File

@ -1,5 +1,5 @@
mod attester_service; mod attester_service;
mod block_producer_service; mod block_producer;
mod config; mod config;
mod duties; mod duties;
pub mod error; pub mod error;

View File

@ -9,15 +9,14 @@
/// 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::BlockProducer;
use crate::block_producer_service::BeaconBlockGrpcClient; 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::{BeaconNodeDuties, DutiesManager, EpochDutiesMap, UpdateOutcome};
use crate::duties::{DutiesManager, EpochDutiesMap};
use crate::error as error_chain; use crate::error as error_chain;
use crate::error::ErrorKind; use crate::error::ErrorKind;
use attester::test_utils::EpochMap; use attester::test_utils::EpochMap;
use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester};
use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer};
use bls::Keypair; use bls::Keypair;
use grpcio::{ChannelBuilder, EnvBuilder}; use grpcio::{ChannelBuilder, EnvBuilder};
use protos::services::Empty; use protos::services::Empty;
@ -35,13 +34,13 @@ use tokio::runtime::Builder;
use tokio::timer::Interval; use tokio::timer::Interval;
use tokio_timer::clock::Clock; use tokio_timer::clock::Clock;
use types::test_utils::generate_deterministic_keypairs; use types::test_utils::generate_deterministic_keypairs;
use types::{Epoch, Fork, Slot}; use types::{ChainSpec, Epoch, Fork, Slot};
//TODO: This service should be simplified in the future. Can be made more steamlined. //TODO: This service should be simplified in the future. Can be made more steamlined.
/// The validator service. This is the main thread that executes and maintains validator /// The validator service. This is the main thread that executes and maintains validator
/// duties. /// duties.
pub struct Service { pub struct Service<B: BeaconNodeDuties + 'static> {
/// The node we currently connected to. /// The node we currently connected to.
connected_node_version: String, connected_node_version: String,
/// The chain id we are processing on. /// The chain id we are processing on.
@ -50,28 +49,25 @@ pub struct Service {
slot_clock: SystemTimeSlotClock, slot_clock: SystemTimeSlotClock,
/// The current slot we are processing. /// The current slot we are processing.
current_slot: Slot, current_slot: Slot,
/// The number of slots per epoch to allow for converting slots to epochs. /// The chain specification for this clients instance.
slots_per_epoch: u64, spec: Arc<ChainSpec>,
/// The duties manager which maintains the state of when to perform actions.
duties_manager: Arc<DutiesManager<B>>,
// GRPC Clients // GRPC Clients
/// The beacon block GRPC client. /// The beacon block GRPC client.
beacon_block_client: Arc<BeaconBlockServiceClient>, beacon_block_client: Arc<BeaconBlockGrpcClient>,
/// The validator GRPC client.
validator_client: Arc<ValidatorServiceClient>,
/// The attester GRPC client. /// The attester GRPC client.
attester_client: Arc<AttestationServiceClient>, attester_client: Arc<AttestationServiceClient>,
/// The validator client logger. /// The validator client logger.
log: slog::Logger, log: slog::Logger,
} }
impl Service { impl<B: BeaconNodeDuties + 'static> Service<B> {
/// Initial connection to the beacon node to determine its properties. /// Initial connection to the beacon node to determine its properties.
/// ///
/// This tries to connect to a beacon node. Once connected, it initialised the gRPC clients /// This tries to connect to a beacon node. Once connected, it initialised the gRPC clients
/// and returns an instance of the service. /// and returns an instance of the service.
fn initialize_service( fn initialize_service(config: ValidatorConfig, log: slog::Logger) -> error_chain::Result<Self> {
config: &ValidatorConfig,
log: slog::Logger,
) -> error_chain::Result<Self> {
// initialise the beacon node client to check for a connection // initialise the beacon node client to check for a connection
let env = Arc::new(EnvBuilder::new().build()); let env = Arc::new(EnvBuilder::new().build());
@ -139,7 +135,9 @@ impl Service {
// Beacon node gRPC beacon block endpoints. // Beacon node gRPC beacon block endpoints.
let beacon_block_client = { let beacon_block_client = {
let ch = ChannelBuilder::new(env.clone()).connect(&config.server); let ch = ChannelBuilder::new(env.clone()).connect(&config.server);
Arc::new(BeaconBlockServiceClient::new(ch)) let beacon_block_service_client = Arc::new(BeaconBlockServiceClient::new(ch));
// a wrapper around the service client to implement the beacon block node trait
Arc::new(BeaconBlockGrpcClient::new(beacon_block_service_client))
}; };
// Beacon node gRPC validator endpoints. // Beacon node gRPC validator endpoints.
@ -164,14 +162,37 @@ impl Service {
.map_err(|e| ErrorKind::SlotClockError(e))? .map_err(|e| ErrorKind::SlotClockError(e))?
.expect("Genesis must be in the future"); .expect("Genesis must be in the future");
let spec = Arc::new(config.spec);
/* Generate the duties manager */
// generate keypairs
// TODO: keypairs are randomly generated; they should be loaded from a file or generated.
// https://github.com/sigp/lighthouse/issues/160
let keypairs = Arc::new(generate_deterministic_keypairs(8));
// Builds a mapping of Epoch -> Map(PublicKey, EpochDuty)
// where EpochDuty contains slot numbers and attestation data that each validator needs to
// produce work on.
let duties_map = RwLock::new(EpochDutiesMap::new(config.spec.slots_per_epoch));
// builds a manager which maintains the list of current duties for all known validators
// and can check when a validator needs to perform a task.
let duties_manager = Arc::new(DutiesManager {
duties_map,
pubkeys: keypairs.iter().map(|keypair| keypair.pk.clone()).collect(),
beacon_node: validator_client,
});
Ok(Self { Ok(Self {
connected_node_version: node_info.version, connected_node_version: node_info.version,
fork, fork,
slot_clock, slot_clock,
current_slot, current_slot,
slots_per_epoch: config.spec.slots_per_epoch, spec,
duties_manager,
beacon_block_client, beacon_block_client,
validator_client,
attester_client, attester_client,
log, log,
}) })
@ -180,7 +201,7 @@ impl Service {
/// Initialise the service then run the core thread. /// Initialise the service then run the core thread.
pub fn start(config: ValidatorConfig, log: slog::Logger) -> error_chain::Result<()> { pub fn start(config: ValidatorConfig, log: slog::Logger) -> error_chain::Result<()> {
// connect to the node and retrieve its properties and initialize the gRPC clients // connect to the node and retrieve its properties and initialize the gRPC clients
let service = Service::initialize_service(&config, log)?; let service = Service::initialize_service(config, log)?;
// we have connected to a node and established its parameters. Spin up the core service // we have connected to a node and established its parameters. Spin up the core service
@ -205,137 +226,95 @@ impl Service {
Interval::new(Instant::now() + duration_to_next_slot, slot_duration) Interval::new(Instant::now() + duration_to_next_slot, slot_duration)
}; };
/* kick off core service */ /* kick off the core service */
// generate keypairs
// TODO: keypairs are randomly generated; they should be loaded from a file or generated.
// https://github.com/sigp/lighthouse/issues/160
let keypairs = Arc::new(generate_deterministic_keypairs(8));
/* build requisite objects to pass to core thread */
// Builds a mapping of Epoch -> Map(PublicKey, EpochDuty)
// where EpochDuty contains slot numbers and attestation data that each validator needs to
// produce work on.
let duties_map = RwLock::new(EpochDutiesMap::new(config.spec.slots_per_epoch));
// builds a manager which maintains the list of current duties for all known validators
// and can check when a validator needs to perform a task.
let manager = Arc::new(DutiesManager {
duties_map,
pubkeys: keypairs.iter().map(|keypair| keypair.pk.clone()).collect(),
beacon_node: service.validator_client.clone(),
});
// run the core thread
runtime.block_on( runtime.block_on(
interval interval
.for_each(move |_| { .for_each(move |_| {
let log = service.log.clone(); // if a non-fatal error occurs, proceed to the next slot.
let _ignore_error = service.per_slot_execution();
/* get the current slot and epoch */ // completed a slot process
let current_slot = match service.slot_clock.present_slot() {
Err(e) => {
error!(log, "SystemTimeError {:?}", e);
return Ok(());
}
Ok(slot) => slot.expect("Genesis is in the future"),
};
let current_epoch = current_slot.epoch(service.slots_per_epoch);
debug_assert!(
current_slot > service.current_slot,
"The Timer should poll a new slot"
);
info!(log, "Processing"; "slot" => current_slot.as_u64(), "epoch" => current_epoch.as_u64());
/* check for new duties */
let cloned_manager = manager.clone();
let cloned_log = log.clone();
// spawn a new thread separate to the runtime
std::thread::spawn(move || {
let _empty_error = cloned_manager.run_update(current_epoch.clone(), cloned_log.clone());
});
/* execute any specified duties */
if let Some(work) = manager.get_current_work(current_slot) {
for (_public_key, work_type) in work {
if work_type.produce_block {
// TODO: Produce a beacon block in a new thread
}
if work_type.attestation_duty.is_some() {
// available AttestationDuty info
let attestation_duty =
work_type.attestation_duty.expect("Cannot be None");
//TODO: Produce an attestation in a new thread
}
}
}
Ok(()) Ok(())
}) })
.map_err(|e| format!("Service thread failed: {:?}", e)), .map_err(|e| format!("Service thread failed: {:?}", e)),
); );
// validator client exited
// completed a slot process
Ok(()) Ok(())
} }
/* /// The execution logic that runs every slot.
// Spawn a new thread to perform block production for the validator. // Errors are logged to output, and core execution continues unless fatal errors occur.
let producer_thread = { fn per_slot_execution(&mut self) -> error_chain::Result<()> {
let spec = spec.clone(); /* get the new current slot and epoch */
let signer = Arc::new(BlockProposerLocalSigner::new(keypair.clone())); self.update_current_slot()?;
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, signer);
let mut block_producer_service = BlockProducerService {
block_producer,
poll_interval_millis,
log,
};
block_producer_service.run(); /* check for new duties */
}) self.check_for_duties();
};
// Spawn a new thread for attestation for the validator. /* process any required duties for validators */
let attester_thread = { self.process_duties();
let signer = Arc::new(AttesterLocalSigner::new(keypair.clone()));
let epoch_map = epoch_map_for_attester.clone();
let slot_clock = slot_clock.clone();
let log = log.clone();
let client = Arc::new(AttestationGrpcClient::new(attester_grpc_client.clone()));
thread::spawn(move || {
let attester = Attester::new(epoch_map, slot_clock, client, signer);
let mut attester_service = AttesterService {
attester,
poll_interval_millis,
log,
};
attester_service.run(); Ok(())
})
};
threads.push((duties_manager_thread, producer_thread, attester_thread));
} }
// Naively wait for all the threads to complete. /// Updates the known current slot and epoch.
for tuple in threads { fn update_current_slot(&mut self) -> error_chain::Result<()> {
let (manager, producer, attester) = tuple; let current_slot = match self.slot_clock.present_slot() {
let _ = producer.join(); Err(e) => {
let _ = manager.join(); error!(self.log, "SystemTimeError {:?}", e);
let _ = attester.join(); return Err("Could not read system time".into());
}
Ok(slot) => slot.expect("Genesis is in the future"),
};
let current_epoch = current_slot.epoch(self.spec.slots_per_epoch);
// this is a fatal error. If the slot clock repeats, there is something wrong with
// the timer, terminate immediately.
assert!(
current_slot > self.current_slot,
"The Timer should poll a new slot"
);
self.current_slot = current_slot;
info!(self.log, "Processing"; "slot" => current_slot.as_u64(), "epoch" => current_epoch.as_u64());
Ok(())
}
/// For all known validator keypairs, update any known duties from the beacon node.
fn check_for_duties(&mut self) {
let cloned_manager = self.duties_manager.clone();
let cloned_log = self.log.clone();
let current_epoch = self.current_slot.epoch(self.spec.slots_per_epoch);
// spawn a new thread separate to the runtime
// TODO: Handle thread termination/timeout
std::thread::spawn(move || {
// the return value is a future which returns ready.
// built to be compatible with the tokio runtime.
let _empty = cloned_manager.run_update(current_epoch.clone(), cloned_log.clone());
});
}
/// If there are any duties to process, spawn a separate thread and perform required actions.
fn process_duties(&mut self) {
if let Some(work) = self.duties_manager.get_current_work(self.current_slot) {
for (_public_key, work_type) in work {
if work_type.produce_block {
// spawns a thread to produce a beacon block
std::thread::spawn(move || {
let block_producer = BlockProducer {
fork: self.fork,
slot: self.current_slot,
spec: self.spec.clone(),
};
});
// TODO: Produce a beacon block in a new thread
}
if work_type.attestation_duty.is_some() {
// available AttestationDuty info
let attestation_duty = work_type.attestation_duty.expect("Cannot be None");
//TODO: Produce an attestation in a new thread
}
}
}
} }
*/
} }