From c107ebf9aa353f74559643d32295584eb43662fd Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 17:06:43 +1100 Subject: [PATCH] Initial implementation of AttestationProducer --- .../beacon_node_attestation.rs | 22 +++ validator_client/src/attester_service/grpc.rs | 45 +++++ validator_client/src/attester_service/mod.rs | 172 +++++++++++++----- .../src/block_producer/beacon_node_block.rs | 31 ++++ validator_client/src/signer.rs | 15 +- 5 files changed, 222 insertions(+), 63 deletions(-) create mode 100644 validator_client/src/attester_service/beacon_node_attestation.rs create mode 100644 validator_client/src/attester_service/grpc.rs create mode 100644 validator_client/src/block_producer/beacon_node_block.rs diff --git a/validator_client/src/attester_service/beacon_node_attestation.rs b/validator_client/src/attester_service/beacon_node_attestation.rs new file mode 100644 index 0000000000..fa8d540319 --- /dev/null +++ b/validator_client/src/attester_service/beacon_node_attestation.rs @@ -0,0 +1,22 @@ +//TODO: generalise these enums to the crate +use super::block_producer::{BeaconNodeError, PublishOutcome}; + +/// Defines the methods required to produce and publish attestations on a Beacon Node. Abstracts the +/// actual beacon node. +pub trait BeaconNodeAttestation: Send + Sync { + /// Request that the node produces the required attestation data. + /// + fn produce_attestation_data( + &self, + slot: Slot, + shard: u64, + ) -> Result; + + /// Request that the node publishes a attestation. + /// + /// Returns `true` if the publish was successful. + fn publish_attestation( + &self, + attestation: Attestation, + ) -> Result; +} diff --git a/validator_client/src/attester_service/grpc.rs b/validator_client/src/attester_service/grpc.rs new file mode 100644 index 0000000000..502e51cac9 --- /dev/null +++ b/validator_client/src/attester_service/grpc.rs @@ -0,0 +1,45 @@ +use protos::services_grpc::AttestationServiceClient; +use std::sync::Arc; + +use attester::{BeaconNode, BeaconNodeError, PublishOutcome}; +use protos::services::ProduceAttestationDataRequest; +use types::{Attestation, AttestationData, Slot}; + +pub struct AttestationGrpcClient { + client: Arc, +} + +impl AttestationGrpcClient { + pub fn new(client: Arc) -> Self { + Self { client } + } +} +/* +impl BeaconNode for AttestationGrpcClient { + fn produce_attestation_data( + &self, + slot: Slot, + shard: u64, + ) -> Result, BeaconNodeError> { + let mut req = ProduceAttestationDataRequest::new(); + req.set_slot(slot.as_u64()); + req.set_shard(shard); + + let reply = self + .client + .produce_attestation_data(&req) + .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + + // TODO: return correct Attestation + Err(BeaconNodeError::DecodeFailure) + } + + fn publish_attestation( + &self, + attestation: Attestation, + ) -> Result { + // TODO: return correct PublishOutcome + Err(BeaconNodeError::DecodeFailure) + } +} +*/ diff --git a/validator_client/src/attester_service/mod.rs b/validator_client/src/attester_service/mod.rs index 1695ec0fb6..7178e28d73 100644 --- a/validator_client/src/attester_service/mod.rs +++ b/validator_client/src/attester_service/mod.rs @@ -1,59 +1,131 @@ mod grpc; -/* -use attester::{Attester, BeaconNode, DutiesReader, PollOutcome as AttesterPollOutcome, Signer}; -use slog::{error, info, warn, Logger}; -use slot_clock::SlotClock; -use std::time::Duration; +mod beacon_node_attestation; -pub use self::attestation_grpc_client::AttestationGrpcClient; +use std::sync::Arc; +use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; -pub struct AttesterService { - pub attester: Attester, - pub poll_interval_millis: u64, - pub log: Logger, +#[derive(Debug, PartialEq)] +pub enum Error { + BeaconNodeError(BeaconNodeError), } +/// This struct contains the logic for requesting and signing beacon attestations for a validator. The +/// validator can abstractly sign via the Signer trait object. +pub struct AttestationProducer<'a, B: BeaconNodeAttestation, S: Signer> { + /// The current fork. + pub fork: Fork, + /// The current slot to produce an attestation for. + pub slot: Slot, + /// The current epoch. + pub spec: Arc, + /// The beacon node to connect to. + pub beacon_node: Arc, + /// The signer to sign the block. + pub signer: &'a S, +} -impl AttesterService { - /// Run a loop which polls the Attester each `poll_interval_millis` millseconds. +impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { + /// Handle outputs and results from attestation production. + pub fn handle_produce_attestation(&mut self, log: slog::Logger) { + match self.produce_attestation() { + Ok(ValidatorEvent::AttestationProduced(_slot)) => { + info!(log, "Attestation produced"; "Validator" => format!("{}", self.signer)) + } + Err(e) => error!(log, "Attestation production error"; "Error" => format!("{:?}", e)), + Ok(ValidatorEvent::SignerRejection(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Signer could not sign the attestation")) + } + Ok(ValidatorEvent::SlashableAttestationNotProduced(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Rejected the attestation as it could have been slashed")) + } + Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to produce an attestation")) + } + } + } + + /// Produce an attestation, sign it and send it back /// - /// Logs the results of the polls. - pub fn run(&mut self) { - loop { - /* We don't do the polling any more... - match self.attester.poll() { - Err(error) => { - error!(self.log, "Attester poll error"; "error" => format!("{:?}", error)) + /// Assumes that an attestation 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. + pub fn produce_attestation(&mut self) -> Result { + let epoch = self.slot.epoch(self.spec.slots_per_epoch); + + if let Some(attestation) = self + .beacon_node + .produce_attestation_data(self.slot, self.shard)? + { + if self.safe_to_produce(&attestation) { + let domain = self.spec.get_domain(epoch, Domain::Attestation, &self.fork); + if let Some(attestation) = self.sign_attestation(attestation, domain) { + self.beacon_node.publish_attestation(attestation)?; + Ok(ValidatorEvent::AttestationProduced(self.slot)) + } else { + Ok(ValidatorEvent::SignerRejection(self.slot)) } - Ok(AttesterPollOutcome::AttestationProduced(slot)) => { - info!(self.log, "Produced Attestation"; "slot" => slot) - } - Ok(AttesterPollOutcome::SlashableAttestationNotProduced(slot)) => { - warn!(self.log, "Slashable attestation was not produced"; "slot" => slot) - } - Ok(AttesterPollOutcome::AttestationNotRequired(slot)) => { - info!(self.log, "Attestation not required"; "slot" => slot) - } - Ok(AttesterPollOutcome::ProducerDutiesUnknown(slot)) => { - error!(self.log, "Attestation duties unknown"; "slot" => slot) - } - Ok(AttesterPollOutcome::SlotAlreadyProcessed(slot)) => { - warn!(self.log, "Attempted to re-process slot"; "slot" => slot) - } - Ok(AttesterPollOutcome::BeaconNodeUnableToProduceAttestation(slot)) => { - error!(self.log, "Beacon node unable to produce attestation"; "slot" => slot) - } - Ok(AttesterPollOutcome::SignerRejection(slot)) => { - error!(self.log, "The cryptographic signer refused to sign the attestation"; "slot" => slot) - } - Ok(AttesterPollOutcome::ValidatorIsUnknown(slot)) => { - error!(self.log, "The Beacon Node does not recognise the validator"; "slot" => slot) - } - }; - */ -println!("Legacy polling still happening..."); -std::thread::sleep(Duration::from_millis(self.poll_interval_millis)); + } else { + Ok(ValidatorEvent::SlashableAttestationNotProduced(self.slot)) + } + } else { + Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(self.slot)) + } + } + + /// Consumes an attestation, returning the attestation signed by the validators private key. + /// + /// Important: this function will not check to ensure the attestation is not slashable. This must be + /// done upstream. + fn sign_attestation(&mut self, mut attestation: Attestation, duties: AttestationDuties, domain: u64) -> Option { + self.store_produce(&attestation); + + // build the aggregate signature + let aggregate_sig = { + let message = AttestationDataAndCustodyBit { + data: attestation.clone(), + custody_bit: false, + }.hash_tree_root(); + + let sig = self.signer.sign_message(&message, domain)?; + + let mut agg_sig = AggregateSignature::new(); + agg_sig.add(&sig); + agg_sig + } + + let mut aggregation_bitfield = Bitfield::with_capacity(duties.comitee_size); + let custody_bitfield = Bitfield::with_capacity(duties.committee_size); + aggregation_bitfield.set(duties.committee_index, true); + + Attestation { + aggregation_bitfield, + data, + custody_bitfield, + aggregate_signature, + } + } + + /// Returns `true` if signing an attestation is safe (non-slashable). + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn safe_to_produce(&self, _block: &Attestation) -> bool { + //TODO: Implement slash protection + true + } + + /// Record that an attestation 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: Implement slash protection + } } -} -} -*/ diff --git a/validator_client/src/block_producer/beacon_node_block.rs b/validator_client/src/block_producer/beacon_node_block.rs new file mode 100644 index 0000000000..65ccb21047 --- /dev/null +++ b/validator_client/src/block_producer/beacon_node_block.rs @@ -0,0 +1,31 @@ +use types::{BeaconBlock, Signature, Slot}; +#[derive(Debug, PartialEq, Clone)] +pub enum BeaconNodeError { + RemoteFailure(String), + DecodeFailure, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum PublishOutcome { + Valid, + InvalidBlock(String), + InvalidAttestation(String), +} + +/// Defines the methods required to produce and publish blocks on a Beacon Node. Abstracts the +/// actual beacon node. +pub trait BeaconNodeBlock: 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, BeaconNodeError>; + + /// Request that the node publishes a block. + /// + /// Returns `true` if the publish was successful. + fn publish_beacon_block(&self, block: BeaconBlock) -> Result; +} diff --git a/validator_client/src/signer.rs b/validator_client/src/signer.rs index 4bbada08ee..018142322f 100644 --- a/validator_client/src/signer.rs +++ b/validator_client/src/signer.rs @@ -3,8 +3,7 @@ use types::{Keypair, PublicKey, Signature}; /// Signs message using an internally-maintained private key. pub trait Signer: Display + Send + Sync + Clone { - fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option; - fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option; + fn sign_message(&self, message: &[u8], domain: u64) -> Option; /// Returns a public key for the signer object. fn to_public(&self) -> PublicKey; } @@ -16,17 +15,7 @@ impl Signer for Keypair { self.pk.clone() } - fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option { + fn sign_message(&self, message: &[u8], domain: u64) -> Option { Some(Signature::new(message, domain, &self.sk)) } - - fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option { - Some(Signature::new(message, domain, &self.sk)) - } - - /* - fn sign_attestation_message(&self, message: &[u8], domain: u64) -> Option { - Some(Signature::new(message, domain, &self.sk)) - } - */ }