From 8f3530f60ce900f41d48c2d407e26cb0a727f9b3 Mon Sep 17 00:00:00 2001 From: thojest Date: Fri, 8 Mar 2019 13:48:33 +0100 Subject: [PATCH 01/34] created attester_service and started to create an attester_thread in main of validator_client (lighthouse-255) --- validator_client/Cargo.toml | 1 + validator_client/src/attester_service/mod.rs | 51 ++++++++++++++++++++ validator_client/src/main.rs | 29 ++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 validator_client/src/attester_service/mod.rs diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index f76772f28..cdde71774 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] block_proposer = { path = "../eth2/block_proposer" } +attester = { path = "../eth2/attester" } bls = { path = "../eth2/utils/bls" } clap = "2.32.0" dirs = "1.0.3" diff --git a/validator_client/src/attester_service/mod.rs b/validator_client/src/attester_service/mod.rs new file mode 100644 index 000000000..fbf8bd005 --- /dev/null +++ b/validator_client/src/attester_service/mod.rs @@ -0,0 +1,51 @@ +use attester::{Attester, BeaconNode, DutiesReader, PollOutcome as AttesterPollOutcome, Signer}; +use slog::Logger; +use slot_clock::SlotClock; +use std::time::Duration; + +pub struct AttesterService { + pub attester: Attester, + pub poll_interval_millis: u64, + pub log: Logger, +} + +impl AttesterService { + /// Run a loop which polls the Attester each `poll_interval_millis` millseconds. + /// + /// Logs the results of the polls. + pub fn run(&mut self) { + loop { + match self.attester.poll() { + Err(error) => { + error!(self.log, "Attester poll error"; "error" => format!("{:?}", error)) + } + 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) + } + }; + + std::thread::sleep(Duration::from_millis(self.poll_interval_millis)); + } + } +} diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index ebab8538c..2e432ceff 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,7 +1,9 @@ use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; +use crate::attester_service::AttesterService; use crate::config::ClientConfig; -use block_proposer::{test_utils::LocalSigner, BlockProducer}; +use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; +use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer}; use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; @@ -13,7 +15,9 @@ use std::sync::Arc; use std::thread; use types::ChainSpec; +mod attester_service; mod block_producer_service; + mod config; mod duties; @@ -160,7 +164,7 @@ fn main() { // Spawn a new thread to perform block production for the validator. let producer_thread = { let spec = spec.clone(); - let signer = Arc::new(LocalSigner::new(keypair.clone())); + let signer = Arc::new(BlockProposerLocalSigner::new(keypair.clone())); let duties_map = duties_map.clone(); let slot_clock = slot_clock.clone(); let log = log.clone(); @@ -178,6 +182,27 @@ fn main() { }) }; + //Spawn a new thread for attestation for the validator. + let attester_thread = { + let signer = Arc::new(AttesterLocalSigner::new(keypair.clone())); + let duties_map = duties_map.clone(); + let slot_clock = slot_clock.clone(); + let log = log.clone(); + //TODO: this is wrong, I assume this has to be AttesterGrpcClient, which has to be defined analogous + // to beacon_block_grpc_client.rs + let client = Arc::new(BeaconBlockGrpcClient::new(beacon_block_grpc_client.clone())); + thread::spawn(move || { + let attester = Attester::new(duties_map, slot_clock, client, signer); + let mut attester_service = AttesterService { + attester, + poll_interval_millis, + log, + }; + + block_producer_service.run(); + }) + }; + threads.push((duties_manager_thread, producer_thread)); } From e942d7533be92f82f9ba4f76a30fa372e58db0d9 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Tue, 12 Mar 2019 21:56:45 +1100 Subject: [PATCH 02/34] A first go at persisting validator keys and handling configuration. Addresses issue #253. - Creates a keystore directory in the config - Fetches serialized keys from the keystore directory - If no keys, generates keys randomly, saves serialized keys to keystore dir. --- eth2/utils/bls/src/keypair.rs | 4 +++ validator_client/Cargo.toml | 1 + validator_client/src/config.rs | 47 +++++++++++++++++++++++++++++++--- validator_client/src/main.rs | 30 ++++++++++++++++++---- 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/eth2/utils/bls/src/keypair.rs b/eth2/utils/bls/src/keypair.rs index d60a2fc25..6feb2a585 100644 --- a/eth2/utils/bls/src/keypair.rs +++ b/eth2/utils/bls/src/keypair.rs @@ -14,4 +14,8 @@ impl Keypair { let pk = PublicKey::from_secret_key(&sk); Keypair { sk, pk } } + + pub fn identifier(&self) -> String { + self.pk.concatenated_hex_id() + } } diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index f76772f28..e9d66f6d3 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -18,3 +18,4 @@ slog = "^2.2.3" slog-term = "^2.4.0" slog-async = "^2.3.0" ssz = { path = "../eth2/utils/ssz" } +bincode = "^1.1.2" diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 68405ed2f..e400f228c 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,4 +1,7 @@ +use bls::Keypair; use std::fs; +use std::fs::File; +use std::io::{Error, ErrorKind}; use std::path::PathBuf; use types::ChainSpec; @@ -6,27 +9,63 @@ use types::ChainSpec; #[derive(Clone)] pub struct ClientConfig { pub data_dir: PathBuf, + pub key_dir: PathBuf, pub server: String, pub spec: ChainSpec, } const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse-validators"; +const DEFAULT_KEYSTORE_SUBDIR: &str = "keystore"; impl ClientConfig { /// Build a new configuration from defaults. - pub fn default() -> Self { + pub fn default() -> Result { let data_dir = { let home = dirs::home_dir().expect("Unable to determine home dir."); home.join(DEFAULT_LIGHTHOUSE_DIR) }; - fs::create_dir_all(&data_dir) - .unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir)); + fs::create_dir_all(&data_dir)?; + + let key_dir = data_dir.join(DEFAULT_KEYSTORE_SUBDIR); + fs::create_dir_all(&key_dir)?; + let server = "localhost:50051".to_string(); let spec = ChainSpec::foundation(); - Self { + Ok(Self { data_dir, + key_dir, server, spec, + }) + } + + // Try to load keys from datadir, or fail + pub fn fetch_keys(&self) -> Result>, Error> { + let mut key_files = fs::read_dir(&self.key_dir)?.peekable(); + + if key_files.peek().is_none() { + return Ok(None); } + + let mut key_pairs: Vec = Vec::new(); + + for key_filename in key_files { + let mut key_file = File::open(key_filename?.path())?; + + let key: Keypair = bincode::deserialize_from(&mut key_file) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + + key_pairs.push(key); + } + + Ok(Some(key_pairs)) + } + + pub fn save_key(&self, key: &Keypair) -> Result<(), Error> { + let key_path = self.key_dir.join(key.identifier() + ".key"); + let mut key_file = File::create(&key_path)?; + bincode::serialize_into(&mut key_file, &key) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + Ok(()) } } diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index ebab8538c..2f2903707 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -6,7 +6,7 @@ use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; use protos::services_grpc::{BeaconBlockServiceClient, ValidatorServiceClient}; -use slog::{error, info, o, Drain}; +use slog::{debug, error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; use std::path::PathBuf; use std::sync::Arc; @@ -17,6 +17,8 @@ mod block_producer_service; mod config; mod duties; +const NUMBER_OF_VALIDATOR_TEST_KEYS: u16 = 3; + fn main() { // Logging let decorator = slog_term::TermDecorator::new().build(); @@ -55,7 +57,7 @@ fn main() { ) .get_matches(); - let mut config = ClientConfig::default(); + let mut config = ClientConfig::default().expect("Unable to create a default configuration."); // Custom datadir if let Some(dir) = matches.value_of("datadir") { @@ -123,9 +125,27 @@ fn main() { * Start threads. */ 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()]; + + let keypairs = config + .fetch_keys() + .expect("Encountered an error while fetching saved keys.") + .unwrap_or_else(|| { + // TODO: Key generation should occur in a separate binary + let mut k = Vec::new(); + info!( + log, + "No key pairs found, generating and saving 3 random key pairs." + ); + for _n in 0..NUMBER_OF_VALIDATOR_TEST_KEYS { + let keypair = Keypair::random(); + config + .save_key(&keypair) + .expect("Unable to save newly generated private key."); + debug!(log, "Keypair generated {:?}", keypair.identifier()); + k.push(keypair); + } + k + }); for keypair in keypairs { info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); From b000a0972e39d9d48f4b5f649f2baf933ef53b93 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Wed, 13 Mar 2019 13:38:28 +1100 Subject: [PATCH 03/34] Minor updates to Paul's comments. - Bubbled up home_dir not found error - Made comment show up in docs --- validator_client/src/config.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index e400f228c..d9d30c64d 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -21,7 +21,8 @@ impl ClientConfig { /// Build a new configuration from defaults. pub fn default() -> Result { let data_dir = { - let home = dirs::home_dir().expect("Unable to determine home dir."); + let home = dirs::home_dir() + .ok_or(Error::new(ErrorKind::NotFound, "Unable to determine home directory."))?; home.join(DEFAULT_LIGHTHOUSE_DIR) }; fs::create_dir_all(&data_dir)?; @@ -39,7 +40,7 @@ impl ClientConfig { }) } - // Try to load keys from datadir, or fail + /// Try to load keys from key_dir, returning None if none are found or an error. pub fn fetch_keys(&self) -> Result>, Error> { let mut key_files = fs::read_dir(&self.key_dir)?.peekable(); From 2215aa4b4640e5bf4a5ace25a184de1dd0ad934c Mon Sep 17 00:00:00 2001 From: thojest Date: Fri, 15 Mar 2019 11:44:39 +0100 Subject: [PATCH 04/34] added protos specification for Attester and created first draft for attestation_grpc_client (lighthouse-255) --- protos/src/services.proto | 47 +++++++++++++++++++ .../attestation_grpc_client.rs | 32 +++++++++++++ validator_client/src/attester_service/mod.rs | 5 +- validator_client/src/main.rs | 21 ++++++--- 4 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 validator_client/src/attester_service/attestation_grpc_client.rs diff --git a/protos/src/services.proto b/protos/src/services.proto index 16e2d4dba..0523cc958 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -23,6 +23,11 @@ service ValidatorService { rpc ValidatorIndex(PublicKey) returns (IndexResponse); } +service AttestationService { + rpc ProduceAttestationData (ProduceAttestationDataRequest) returns (ProduceAttestationDataResponse); + rpc PublishAttestationData (PublishAttestationDataRequest) returns (PublishAttestationDataResponse); +} + message BeaconBlock { uint64 slot = 1; bytes block_root = 2; @@ -30,6 +35,30 @@ message BeaconBlock { bytes signature = 4; } +message Crosslink { + uint64 epoch = 1; + bytes crosslink_data_root = 2; + +} + +message AttestationData { + uint64 slot = 1; + uint64 shard = 2; + bytes beacon_block_root = 3; + bytes epoch_boundary_root = 4; + bytes crosslink_data_root = 5; + Crosslink latest_crosslink = 6; + uint64 justified_epoch = 7; + bytes justified_block_root = 8; + +} + +message FreeAttestation { + AttestationData attestation_data = 1; + bytes signature = 2; + uint64 validator_index = 3; +} + // Validator requests an unsigned proposal. message ProduceBeaconBlockRequest { uint64 slot = 1; @@ -51,6 +80,24 @@ message PublishBeaconBlockResponse { bytes msg = 2; } +message ProduceAttestationDataRequest { + uint64 slot = 1; + uint64 shard = 2; +} + +message ProduceAttestationDataResponse { + AttestationData attestation_data = 1; +} + +message PublishAttestationDataRequest { + FreeAttestation free_attestation = 1; +} + +message PublishAttestationDataResponse { + bool success = 1; + bytes msg = 2; +} + // A validators duties for some epoch. // TODO: add shard duties. message ValidatorAssignment { diff --git a/validator_client/src/attester_service/attestation_grpc_client.rs b/validator_client/src/attester_service/attestation_grpc_client.rs new file mode 100644 index 000000000..b3a0bd134 --- /dev/null +++ b/validator_client/src/attester_service/attestation_grpc_client.rs @@ -0,0 +1,32 @@ +use protos::services_grpc::AttestationServiceClient; +use std::sync::Arc; + +use attester::{BeaconNode, BeaconNodeError, PublishOutcome}; +use types::{AttestationData, FreeAttestation, 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> { + Err(BeaconNodeError::DecodeFailure) + } + + fn publish_attestation_data( + &self, + free_attestation: FreeAttestation, + ) -> Result { + Err(BeaconNodeError::DecodeFailure) + } +} diff --git a/validator_client/src/attester_service/mod.rs b/validator_client/src/attester_service/mod.rs index fbf8bd005..fe5de7647 100644 --- a/validator_client/src/attester_service/mod.rs +++ b/validator_client/src/attester_service/mod.rs @@ -1,8 +1,11 @@ +mod attestation_grpc_client; use attester::{Attester, BeaconNode, DutiesReader, PollOutcome as AttesterPollOutcome, Signer}; -use slog::Logger; +use slog::{error, info, warn, Logger}; use slot_clock::SlotClock; use std::time::Duration; +pub use self::attestation_grpc_client::AttestationGrpcClient; + pub struct AttesterService { pub attester: Attester, pub poll_interval_millis: u64, diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 2e432ceff..60bc76553 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,13 +1,15 @@ use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; -use crate::attester_service::AttesterService; +use crate::attester_service::{AttestationGrpcClient, AttesterService}; use crate::config::ClientConfig; use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer}; use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; -use protos::services_grpc::{BeaconBlockServiceClient, ValidatorServiceClient}; +use protos::services_grpc::{ + AttestationServiceClient, BeaconBlockServiceClient, ValidatorServiceClient, +}; use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; use std::path::PathBuf; @@ -106,6 +108,13 @@ fn main() { Arc::new(ValidatorServiceClient::new(ch)) }; + //Beacon node gRPC attester endpoints. + let attester_grpc_client = { + let env = Arc::new(EnvBuilder::new().build()); + let ch = ChannelBuilder::new(env).connect(&config.server); + Arc::new(AttestationServiceClient::new(ch)) + }; + // Spec let spec = Arc::new(config.spec.clone()); @@ -182,15 +191,13 @@ fn main() { }) }; - //Spawn a new thread for attestation for the validator. + // Spawn a new thread for attestation for the validator. let attester_thread = { let signer = Arc::new(AttesterLocalSigner::new(keypair.clone())); let duties_map = duties_map.clone(); let slot_clock = slot_clock.clone(); let log = log.clone(); - //TODO: this is wrong, I assume this has to be AttesterGrpcClient, which has to be defined analogous - // to beacon_block_grpc_client.rs - let client = Arc::new(BeaconBlockGrpcClient::new(beacon_block_grpc_client.clone())); + let client = Arc::new(AttestationGrpcClient::new(attester_grpc_client.clone())); thread::spawn(move || { let attester = Attester::new(duties_map, slot_clock, client, signer); let mut attester_service = AttesterService { @@ -199,7 +206,7 @@ fn main() { log, }; - block_producer_service.run(); + attester_service.run(); }) }; From d8099ae00c735058a88ea8205bfede7ee8c50683 Mon Sep 17 00:00:00 2001 From: thojest Date: Mon, 18 Mar 2019 21:12:06 +0100 Subject: [PATCH 05/34] started implementing BeaconNode for AttestationGrpcClient; included correct epoch_map for instantiation of Attester (lighthouse-255) --- .../attester_service/attestation_grpc_client.rs | 12 ++++++++++++ validator_client/src/main.rs | 14 ++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/validator_client/src/attester_service/attestation_grpc_client.rs b/validator_client/src/attester_service/attestation_grpc_client.rs index b3a0bd134..566d74a39 100644 --- a/validator_client/src/attester_service/attestation_grpc_client.rs +++ b/validator_client/src/attester_service/attestation_grpc_client.rs @@ -2,6 +2,7 @@ use protos::services_grpc::AttestationServiceClient; use std::sync::Arc; use attester::{BeaconNode, BeaconNodeError, PublishOutcome}; +use protos::services::ProduceAttestationDataRequest; use types::{AttestationData, FreeAttestation, Slot}; pub struct AttestationGrpcClient { @@ -20,6 +21,16 @@ impl BeaconNode for AttestationGrpcClient { 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 AttestationData Err(BeaconNodeError::DecodeFailure) } @@ -27,6 +38,7 @@ impl BeaconNode for AttestationGrpcClient { &self, free_attestation: FreeAttestation, ) -> Result { + // TODO: return correct PublishOutcome Err(BeaconNodeError::DecodeFailure) } } diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 60bc76553..4664d5dc9 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -2,6 +2,7 @@ use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; use crate::attester_service::{AttestationGrpcClient, AttesterService}; use crate::config::ClientConfig; +use attester::test_utils::EpochMap; use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer}; use bls::Keypair; @@ -19,7 +20,6 @@ use types::ChainSpec; mod attester_service; mod block_producer_service; - mod config; mod duties; @@ -143,6 +143,7 @@ fn main() { for keypair in keypairs { info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); let duties_map = Arc::new(EpochDutiesMap::new(spec.slots_per_epoch)); + let epoch_map_for_attester = Arc::new(EpochMap::new(spec.slots_per_epoch)); // Spawn a new thread to maintain the validator's `EpochDuties`. let duties_manager_thread = { @@ -191,15 +192,15 @@ fn main() { }) }; - // Spawn a new thread for attestation for the validator. + // Spawn a new thread for attestation for the validator. let attester_thread = { let signer = Arc::new(AttesterLocalSigner::new(keypair.clone())); - let duties_map = duties_map.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(duties_map, slot_clock, client, signer); + let attester = Attester::new(epoch_map, slot_clock, client, signer); let mut attester_service = AttesterService { attester, poll_interval_millis, @@ -210,13 +211,14 @@ fn main() { }) }; - threads.push((duties_manager_thread, producer_thread)); + threads.push((duties_manager_thread, producer_thread, attester_thread)); } // Naively wait for all the threads to complete. for tuple in threads { - let (manager, producer) = tuple; + let (manager, producer, attester) = tuple; let _ = producer.join(); let _ = manager.join(); + let _ = attester.join(); } } From 49f6e7ac659f7daa70a6939f99332976e9bac2af Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Wed, 20 Mar 2019 16:23:33 +1100 Subject: [PATCH 06/34] Moved configuration around for validator client. - Custom datadir/server argument logic moved into configuration, out of main. - Updated the validator config directory structure, as per issue #253 suggestions - Removed the 'generate 3 random keys' function - Updated the README to reflect new structure - Just exit if there are no keys, don't generate any (this is for accounts_manager, in a separate commit). - Created a lib.rs file, so that the validator client configuration can be included by external crates. --- validator_client/Cargo.toml | 9 +++ validator_client/README.md | 29 +++++++-- validator_client/src/config.rs | 107 +++++++++++++++++++++++++-------- validator_client/src/lib.rs | 3 + validator_client/src/main.rs | 72 +++++----------------- 5 files changed, 136 insertions(+), 84 deletions(-) create mode 100644 validator_client/src/lib.rs diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index e9d66f6d3..327fab22b 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -4,6 +4,15 @@ version = "0.1.0" authors = ["Paul Hauner "] edition = "2018" +[[bin]] +name = "validator_client" +path = "src/main.rs" + +[lib] +name = "validator_client" +path = "src/lib.rs" + + [dependencies] block_proposer = { path = "../eth2/block_proposer" } bls = { path = "../eth2/utils/bls" } diff --git a/validator_client/README.md b/validator_client/README.md index aa84fe013..109d9f317 100644 --- a/validator_client/README.md +++ b/validator_client/README.md @@ -57,10 +57,31 @@ complete and return a block from the BN. ### Configuration -Presently the validator specifics (pubkey, etc.) are randomly generated and the -chain specification (slot length, BLS domain, etc.) are fixed to foundation -parameters. This is temporary and will be upgrade so these parameters can be -read from file (or initialized on first-boot). +Validator configurations are stored in a separate data directory from the main Beacon Node +binary. The validator data directory defaults to: +`$HOME/.lighthouse-validator`, however an alternative can be specified on the command line +with `--datadir`. + +The configuration directory structure looks like: +``` +~/.lighthouse-validator +└── validators + ├── 3cf4210d58ec + │   └── private.key + ├── 9b5d8b5be4e7 + │   └── private.key + └── cf6e07188f48 + └── private.key +``` + +Where the hex value of the directory is a portion of the validator public key. + +Validator keys must be generated using the separate `accounts_manager` binary, which will +place the keys into this directory structure in a format compatible with the validator client. + +The chain specification (slot length, BLS domain, etc.) defaults to foundation +parameters, however is temporary and an upgrade will allow these parameters to be +read from a file (or initialized on first-boot). ## BN Communication diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index d9d30c64d..2e109c69d 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,72 +1,131 @@ use bls::Keypair; +use clap::ArgMatches; use std::fs; use std::fs::File; use std::io::{Error, ErrorKind}; use std::path::PathBuf; use types::ChainSpec; +use bincode; /// Stores the core configuration for this validator instance. #[derive(Clone)] -pub struct ClientConfig { +pub struct ValidatorClientConfig { + /// The data directory, which stores all validator databases pub data_dir: PathBuf, - pub key_dir: PathBuf, + /// The directory where the individual validator configuration directories are stored. + pub validator_dir: PathBuf, + /// The server at which the Beacon Node can be contacted pub server: String, + /// The chain specification that we are connecting to pub spec: ChainSpec, } -const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse-validators"; -const DEFAULT_KEYSTORE_SUBDIR: &str = "keystore"; +const DEFAULT_VALIDATOR_DATADIR: &str = ".lighthouse-validator"; +const DEFAULT_VALIDATORS_SUBDIR: &str = "validators"; +const DEFAULT_PRIVATE_KEY_FILENAME: &str = "private.key"; -impl ClientConfig { - /// Build a new configuration from defaults. - pub fn default() -> Result { - let data_dir = { - let home = dirs::home_dir() - .ok_or(Error::new(ErrorKind::NotFound, "Unable to determine home directory."))?; - home.join(DEFAULT_LIGHTHOUSE_DIR) +impl ValidatorClientConfig { + /// Build a new configuration from defaults, which are overrided by arguments provided. + pub fn build_config(arguments: &ArgMatches) -> Result { + // Use the specified datadir, or default in the home directory + let data_dir: PathBuf = match arguments.value_of("datadir") { + Some(path) => PathBuf::from(path.to_string()), + None => { + let home = dirs::home_dir().ok_or_else(|| Error::new( + ErrorKind::NotFound, + "Unable to determine home directory.", + ))?; + home.join(DEFAULT_VALIDATOR_DATADIR) + } }; fs::create_dir_all(&data_dir)?; - let key_dir = data_dir.join(DEFAULT_KEYSTORE_SUBDIR); - fs::create_dir_all(&key_dir)?; + let validator_dir = data_dir.join(DEFAULT_VALIDATORS_SUBDIR); + fs::create_dir_all(&validator_dir)?; + + let server: String = match arguments.value_of("server") { + Some(srv) => { + //TODO: I don't think this parses correctly a server & port combo + srv.parse::() + .map_err(|e| Error::new(ErrorKind::InvalidInput, e))? + .to_string() + } + None => "localhost:50051".to_string(), + }; + + // TODO: Permit loading a custom spec from file. + let spec: ChainSpec = match arguments.value_of("spec") { + Some(spec_str) => { + match spec_str { + "foundation" => ChainSpec::foundation(), + "few_validators" => ChainSpec::few_validators(), + // Should be impossible due to clap's `possible_values(..)` function. + _ => unreachable!(), + } + } + None => ChainSpec::foundation(), + }; - let server = "localhost:50051".to_string(); - let spec = ChainSpec::foundation(); Ok(Self { data_dir, - key_dir, + validator_dir, server, spec, }) } - /// Try to load keys from key_dir, returning None if none are found or an error. + /// Try to load keys from validator_dir, returning None if none are found or an error. pub fn fetch_keys(&self) -> Result>, Error> { - let mut key_files = fs::read_dir(&self.key_dir)?.peekable(); + let mut validator_dirs = fs::read_dir(&self.validator_dir)?.peekable(); - if key_files.peek().is_none() { + // There are no validator directories. + if validator_dirs.peek().is_none() { return Ok(None); } let mut key_pairs: Vec = Vec::new(); - for key_filename in key_files { - let mut key_file = File::open(key_filename?.path())?; + for validator_dir_result in validator_dirs { + let validator_dir = validator_dir_result?; + + // Try to open the key file directly + // TODO skip keyfiles that are not found, and log the error instead of returning it. + let mut key_file = File::open(validator_dir.path().join(DEFAULT_PRIVATE_KEY_FILENAME))?; let key: Keypair = bincode::deserialize_from(&mut key_file) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + // TODO skip keyfile if it's not matched, and log the error instead of returning it. + let validator_directory_name = validator_dir.file_name().into_string().map_err(|_| { + Error::new( + ErrorKind::InvalidData, + "The filename cannot be parsed to a string.", + ) + })?; + if key.identifier() != validator_directory_name { + return Err(Error::new( + ErrorKind::InvalidData, + "The validator directory ID did not match the key found inside.", + )); + } + key_pairs.push(key); } Ok(Some(key_pairs)) } - pub fn save_key(&self, key: &Keypair) -> Result<(), Error> { - let key_path = self.key_dir.join(key.identifier() + ".key"); + /// Saves a keypair to a file inside the appropriate validator directory. Returns the saved path filename. + pub fn save_key(&self, key: &Keypair) -> Result { + let validator_config_path = self.validator_dir.join(key.identifier()); + let key_path = validator_config_path.join(DEFAULT_PRIVATE_KEY_FILENAME); + + fs::create_dir_all(&validator_config_path)?; + let mut key_file = File::create(&key_path)?; + bincode::serialize_into(&mut key_file, &key) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - Ok(()) + Ok(key_path) } } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs new file mode 100644 index 000000000..133ccd932 --- /dev/null +++ b/validator_client/src/lib.rs @@ -0,0 +1,3 @@ +pub mod config; + +pub use crate::config::ValidatorClientConfig; \ No newline at end of file diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 2f2903707..99523ef9f 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,24 +1,19 @@ use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; -use crate::config::ClientConfig; +use crate::config::ValidatorClientConfig; use block_proposer::{test_utils::LocalSigner, BlockProducer}; -use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; use protos::services_grpc::{BeaconBlockServiceClient, ValidatorServiceClient}; -use slog::{debug, error, info, o, Drain}; +use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; -use std::path::PathBuf; use std::sync::Arc; -use std::thread; -use types::ChainSpec; +use std::{thread, process, time}; mod block_producer_service; mod config; mod duties; -const NUMBER_OF_VALIDATOR_TEST_KEYS: u16 = 3; - fn main() { // Logging let decorator = slog_term::TermDecorator::new().build(); @@ -57,36 +52,11 @@ fn main() { ) .get_matches(); - let mut config = ClientConfig::default().expect("Unable to create a default configuration."); - - // Custom datadir - if let Some(dir) = matches.value_of("datadir") { - config.data_dir = PathBuf::from(dir.to_string()); - } - - // Custom server port - if let Some(server_str) = matches.value_of("server") { - if let Ok(addr) = server_str.parse::() { - config.server = addr.to_string(); - } else { - error!(log, "Invalid address"; "server" => server_str); - return; - } - } - - // TODO: Permit loading a custom spec from file. - // Custom spec - if let Some(spec_str) = matches.value_of("spec") { - match spec_str { - "foundation" => config.spec = ChainSpec::foundation(), - "few_validators" => config.spec = ChainSpec::few_validators(), - // Should be impossible due to clap's `possible_values(..)` function. - _ => unreachable!(), - }; - } + let config = ValidatorClientConfig::build_config(&matches) + .expect("Unable to build a configuration for the validator client."); // Log configuration - info!(log, ""; + info!(log, "Configuration parameters:"; "data_dir" => &config.data_dir.to_str(), "server" => &config.server); @@ -121,31 +91,21 @@ fn main() { let poll_interval_millis = spec.seconds_per_slot * 1000 / 10; // 10% epoch time precision. info!(log, "Starting block producer service"; "polls_per_epoch" => spec.seconds_per_slot * 1000 / poll_interval_millis); + let keypairs = config + .fetch_keys() + .expect("Encountered an error while fetching saved keys.") + .unwrap_or_else(|| { + error!(log, "No key pairs found in configuration, they must first be generated with: account_manager generate."); + // give the logger a chance to flush the error before exiting. + thread::sleep(time::Duration::from_millis(500)); + process::exit(1) + }); + /* * Start threads. */ let mut threads = vec![]; - let keypairs = config - .fetch_keys() - .expect("Encountered an error while fetching saved keys.") - .unwrap_or_else(|| { - // TODO: Key generation should occur in a separate binary - let mut k = Vec::new(); - info!( - log, - "No key pairs found, generating and saving 3 random key pairs." - ); - for _n in 0..NUMBER_OF_VALIDATOR_TEST_KEYS { - let keypair = Keypair::random(); - config - .save_key(&keypair) - .expect("Unable to save newly generated private key."); - debug!(log, "Keypair generated {:?}", keypair.identifier()); - k.push(keypair); - } - k - }); for keypair in keypairs { info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); From dc2fc7a2505a7c4835fb4dd8076780cc4850ed6c Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Wed, 20 Mar 2019 16:24:28 +1100 Subject: [PATCH 07/34] Added a new binary, accounts_manager. - Updated main Cargo.toml file - Created a new readme & binary - Includes configuration options from the validator, which now has it's config as a library. --- Cargo.toml | 1 + account_manager/Cargo.toml | 13 +++++++++ account_manager/README.md | 24 +++++++++++++++ account_manager/src/main.rs | 58 +++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 account_manager/Cargo.toml create mode 100644 account_manager/README.md create mode 100644 account_manager/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index c5aae7f43..a43b1052e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,4 +23,5 @@ members = [ "beacon_node/beacon_chain/test_harness", "protos", "validator_client", + "account_manager", ] diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml new file mode 100644 index 000000000..c26d4b70a --- /dev/null +++ b/account_manager/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "account_manager" +version = "0.0.1" +authors = ["Luke Anderson "] +edition = "2018" + +[dependencies] +bls = { path = "../eth2/utils/bls" } +clap = "2.32.0" +slog = "^2.2.3" +slog-term = "^2.4.0" +slog-async = "^2.3.0" +validator_client = { path = "../validator_client" } diff --git a/account_manager/README.md b/account_manager/README.md new file mode 100644 index 000000000..bf8891f40 --- /dev/null +++ b/account_manager/README.md @@ -0,0 +1,24 @@ +# Lighthouse Accounts Manager + +The accounts manager (AM) is a stand-alone binary which allows +users to generate and manage the cryptographic keys necessary to +interact with Ethereum Serenity. + +## Roles + +The AM is responsible for the following tasks: +- Generation of cryptographic key pairs + - Must acquire sufficient entropy to ensure keys are generated securely (TBD) +- Secure storage of private keys + - Keys must be encrypted while at rest on the disk (TBD) + - The format is compatible with the validator client +- Produces messages and transactions necessary to initiate +staking on Ethereum 1.x (TPD) + + +## Implementation + +The AM is not a service, and does not run continuously, nor does it +interact with any running services. +It is intended to be executed separately from other Lighthouse binaries +and produce files which can be consumed by them. \ No newline at end of file diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs new file mode 100644 index 000000000..03546c95b --- /dev/null +++ b/account_manager/src/main.rs @@ -0,0 +1,58 @@ +use bls::Keypair; +use clap::{App, Arg, SubCommand}; +use slog::{debug, info, o, Drain}; +use std::path::PathBuf; +use validator_client::config::ValidatorClientConfig; + +fn main() { + // Logging + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::CompactFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + let log = slog::Logger::root(drain, o!()); + + // CLI + let matches = App::new("Lighthouse Accounts Manager") + .version("0.0.1") + .author("Sigma Prime ") + .about("Eth 2.0 Accounts Manager") + .arg( + Arg::with_name("datadir") + .long("datadir") + .value_name("DIR") + .help("Data directory for keys and databases.") + .takes_value(true), + ) + .subcommand( + SubCommand::with_name("generate") + .about("Generates a new validator private key") + .version("0.0.1") + .author("Sigma Prime "), + ) + .get_matches(); + + let config = ValidatorClientConfig::build_config(&matches) + .expect("Unable to build a configuration for the account manager."); + + // Log configuration + info!(log, ""; + "data_dir" => &config.data_dir.to_str()); + + match matches.subcommand() { + ("generate", Some(_gen_m)) => { + let keypair = Keypair::random(); + let key_path: PathBuf = config + .save_key(&keypair) + .expect("Unable to save newly generated private key."); + debug!( + log, + "Keypair generated {:?}, saved to: {:?}", + keypair.identifier(), + key_path.to_string_lossy() + ); + } + _ => panic!( + "The account manager must be run with a subcommand. See help for more information." + ), + } +} From 9e47cb56e754f7c61d562317ff0045449ddf49c6 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Wed, 20 Mar 2019 16:27:58 +1100 Subject: [PATCH 08/34] Fixed code appearance with rustfmt. --- validator_client/src/config.rs | 14 +++++++------- validator_client/src/lib.rs | 2 +- validator_client/src/main.rs | 3 +-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 2e109c69d..d056136c9 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,3 +1,4 @@ +use bincode; use bls::Keypair; use clap::ArgMatches; use std::fs; @@ -5,7 +6,6 @@ use std::fs::File; use std::io::{Error, ErrorKind}; use std::path::PathBuf; use types::ChainSpec; -use bincode; /// Stores the core configuration for this validator instance. #[derive(Clone)] @@ -31,10 +31,9 @@ impl ValidatorClientConfig { let data_dir: PathBuf = match arguments.value_of("datadir") { Some(path) => PathBuf::from(path.to_string()), None => { - let home = dirs::home_dir().ok_or_else(|| Error::new( - ErrorKind::NotFound, - "Unable to determine home directory.", - ))?; + let home = dirs::home_dir().ok_or_else(|| { + Error::new(ErrorKind::NotFound, "Unable to determine home directory.") + })?; home.join(DEFAULT_VALIDATOR_DATADIR) } }; @@ -96,13 +95,14 @@ impl ValidatorClientConfig { .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; // TODO skip keyfile if it's not matched, and log the error instead of returning it. - let validator_directory_name = validator_dir.file_name().into_string().map_err(|_| { + let validator_directory_name = + validator_dir.file_name().into_string().map_err(|_| { Error::new( ErrorKind::InvalidData, "The filename cannot be parsed to a string.", ) })?; - if key.identifier() != validator_directory_name { + if key.identifier() != validator_directory_name { return Err(Error::new( ErrorKind::InvalidData, "The validator directory ID did not match the key found inside.", diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 133ccd932..60361c051 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -1,3 +1,3 @@ pub mod config; -pub use crate::config::ValidatorClientConfig; \ No newline at end of file +pub use crate::config::ValidatorClientConfig; diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 99523ef9f..1830bd1a4 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -8,7 +8,7 @@ use protos::services_grpc::{BeaconBlockServiceClient, ValidatorServiceClient}; use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; use std::sync::Arc; -use std::{thread, process, time}; +use std::{process, thread, time}; mod block_producer_service; mod config; @@ -106,7 +106,6 @@ fn main() { */ let mut threads = vec![]; - for keypair in keypairs { info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); let duties_map = Arc::new(EpochDutiesMap::new(spec.slots_per_epoch)); From e758e717533f22ca73223da2047d7bb547cc9f47 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 12:13:48 +1100 Subject: [PATCH 09/34] Tidy services.proto --- protos/src/services.proto | 180 +++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 82 deletions(-) diff --git a/protos/src/services.proto b/protos/src/services.proto index 0523cc958..79fffd088 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -12,22 +12,52 @@ syntax = "proto3"; package ethereum.beacon.rpc.v1; + +/// Service that handles block production service BeaconBlockService { rpc ProduceBeaconBlock(ProduceBeaconBlockRequest) returns (ProduceBeaconBlockResponse); rpc PublishBeaconBlock(PublishBeaconBlockRequest) returns (PublishBeaconBlockResponse); } +/// Service that provides the validator client with requisite knowledge about +//its public keys service ValidatorService { - // rpc ValidatorAssignment(ValidatorAssignmentRequest) returns (ValidatorAssignmentResponse); rpc ProposeBlockSlot(ProposeBlockSlotRequest) returns (ProposeBlockSlotResponse); rpc ValidatorIndex(PublicKey) returns (IndexResponse); + // rpc ValidatorAssignment(ValidatorAssignmentRequest) returns (ValidatorAssignmentResponse); } +/// Service that handles validator attestations service AttestationService { rpc ProduceAttestationData (ProduceAttestationDataRequest) returns (ProduceAttestationDataResponse); rpc PublishAttestationData (PublishAttestationDataRequest) returns (PublishAttestationDataResponse); } +/* + * Block Production Service Messages + */ + +// Validator requests an unsigned proposal. +message ProduceBeaconBlockRequest { + uint64 slot = 1; +} + +// Beacon node returns an unsigned proposal. +message ProduceBeaconBlockResponse { + BeaconBlock block = 1; +} + +// Validator submits a signed proposal. +message PublishBeaconBlockRequest { + BeaconBlock block = 1; +} + +// Beacon node indicates a sucessfully submitted proposal. +message PublishBeaconBlockResponse { + bool success = 1; + bytes msg = 2; +} + message BeaconBlock { uint64 slot = 1; bytes block_root = 2; @@ -35,6 +65,73 @@ message BeaconBlock { bytes signature = 4; } +/* + * Validator Service Messages + */ +/* +message ValidatorAssignmentRequest { + uint64 epoch = 1; + bytes validator_index = 2; +} + +// A validators duties for some epoch. +// TODO: add shard duties. +message ValidatorAssignment { + oneof block_production_slot_oneof { + bool block_production_slot_none = 1; + uint64 block_production_slot = 2; + } +} +*/ + +// Validator Assignment + +message PublicKey { + bytes public_key = 1; +} + +message IndexResponse { + uint64 index = 1; +} + + +// Propose slot + +message ProposeBlockSlotRequest { + uint64 epoch = 1; + uint64 validator_index = 2; +} + +message ProposeBlockSlotResponse { + oneof slot_oneof { + bool none = 1; + uint64 slot = 2; + } +} + + +/* + * Attestation Service Messages + */ + +message ProduceAttestationDataRequest { + uint64 slot = 1; + uint64 shard = 2; +} + +message ProduceAttestationDataResponse { + AttestationData attestation_data = 1; +} + +message PublishAttestationDataRequest { + FreeAttestation free_attestation = 1; +} + +message PublishAttestationDataResponse { + bool success = 1; + bytes msg = 2; +} + message Crosslink { uint64 epoch = 1; bytes crosslink_data_root = 2; @@ -58,84 +155,3 @@ message FreeAttestation { bytes signature = 2; uint64 validator_index = 3; } - -// Validator requests an unsigned proposal. -message ProduceBeaconBlockRequest { - uint64 slot = 1; -} - -// Beacon node returns an unsigned proposal. -message ProduceBeaconBlockResponse { - BeaconBlock block = 1; -} - -// Validator submits a signed proposal. -message PublishBeaconBlockRequest { - BeaconBlock block = 1; -} - -// Beacon node indicates a sucessfully submitted proposal. -message PublishBeaconBlockResponse { - bool success = 1; - bytes msg = 2; -} - -message ProduceAttestationDataRequest { - uint64 slot = 1; - uint64 shard = 2; -} - -message ProduceAttestationDataResponse { - AttestationData attestation_data = 1; -} - -message PublishAttestationDataRequest { - FreeAttestation free_attestation = 1; -} - -message PublishAttestationDataResponse { - bool success = 1; - bytes msg = 2; -} - -// A validators duties for some epoch. -// TODO: add shard duties. -message ValidatorAssignment { - oneof block_production_slot_oneof { - bool block_production_slot_none = 1; - uint64 block_production_slot = 2; - } -} - -message ValidatorAssignmentRequest { - uint64 epoch = 1; - bytes validator_index = 2; -} - -/* - * Propose slot - */ - -message ProposeBlockSlotRequest { - uint64 epoch = 1; - uint64 validator_index = 2; -} - -message ProposeBlockSlotResponse { - oneof slot_oneof { - bool none = 1; - uint64 slot = 2; - } -} - -/* - * Validator Assignment - */ - -message PublicKey { - bytes public_key = 1; -} - -message IndexResponse { - uint64 index = 1; -} From 0a59a73894b2c3294da08f7a8cc35150f5e24165 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 12:36:45 +1100 Subject: [PATCH 10/34] Add BeaconNodeService to RPC --- protos/src/services.proto | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/protos/src/services.proto b/protos/src/services.proto index 79fffd088..45fcfb120 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -12,6 +12,10 @@ syntax = "proto3"; package ethereum.beacon.rpc.v1; +// Service that currently identifies a beacon node +service BeaconNodeService { + rpc Info(Empty) returns (NodeInfo); +} /// Service that handles block production service BeaconBlockService { @@ -33,6 +37,26 @@ service AttestationService { rpc PublishAttestationData (PublishAttestationDataRequest) returns (PublishAttestationDataResponse); } +/* + * Beacon Node Service Message + */ +message NodeInfo { + string version = 1; + Fork fork = 2; + uint32 chain_id = 3; +} + +message Fork { + bytes previous_version = 1; + bytes current_version = 2; + uint64 epoch = 3; +} + +message Empty { +} + + + /* * Block Production Service Messages */ From 844fdc0fb9122c6ddd21acb849d78421e3560c8e Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 12:39:45 +1100 Subject: [PATCH 11/34] Rename network_id to chain_id --- beacon_node/network/src/sync/simple_sync.rs | 8 ++++---- eth2/types/src/chain_spec.rs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 0f7de6ab9..d3d0e1475 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -36,7 +36,7 @@ pub struct SimpleSync { /// The current state of the syncing protocol. state: SyncState, /// The network id, for quick HELLO RPC message lookup. - network_id: u8, + chain_id: u8, /// The latest epoch of the syncing chain. latest_finalized_epoch: Epoch, /// The latest block of the syncing chain. @@ -53,7 +53,7 @@ impl SimpleSync { chain: beacon_chain.clone(), known_peers: HashMap::new(), state: SyncState::Idle, - network_id: beacon_chain.get_spec().network_id, + chain_id: beacon_chain.get_spec().chain_id, latest_finalized_epoch: state.finalized_epoch, latest_slot: state.slot - 1, //TODO: Build latest block function into Beacon chain and correct this log: sync_logger, @@ -65,7 +65,7 @@ impl SimpleSync { let state = &self.chain.get_state(); //TODO: Paul to verify the logic of these fields. HelloMessage { - network_id: self.network_id, + network_id: self.chain_id, latest_finalized_root: state.finalized_root, latest_finalized_epoch: state.finalized_epoch, best_root: Hash256::zero(), //TODO: build correct value as a beacon chain function @@ -75,7 +75,7 @@ impl SimpleSync { pub fn validate_peer(&mut self, peer_id: PeerId, hello_message: HelloMessage) -> bool { // network id must match - if hello_message.network_id != self.network_id { + if hello_message.network_id != self.chain_id { return false; } // compare latest epoch and finalized root to see if they exist in our chain diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index 65ea5c4d4..5fa28b6ac 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -118,7 +118,7 @@ pub struct ChainSpec { * */ pub boot_nodes: Vec, - pub network_id: u8, + pub chain_id: u8, } impl ChainSpec { @@ -255,7 +255,7 @@ impl ChainSpec { * Boot nodes */ boot_nodes: vec![], - network_id: 1, // foundation network id + chain_id: 1, // foundation chain id } } @@ -272,7 +272,7 @@ impl ChainSpec { Self { boot_nodes, - network_id: 2, // lighthouse testnet network id + chain_id: 2, // lighthouse testnet chain id ..ChainSpec::few_validators() } } From ee6a0ccb92bca9a49b023533155e8b837ab8bfe0 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 13:37:24 +1100 Subject: [PATCH 12/34] Initial build of server-side BeaconNodeService RPC --- beacon_node/client/src/lib.rs | 2 +- beacon_node/rpc/Cargo.toml | 8 ++--- beacon_node/rpc/src/beacon_node.rs | 50 ++++++++++++++++++++++++++++++ beacon_node/rpc/src/lib.rs | 28 +++++++++++++++-- 4 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 beacon_node/rpc/src/beacon_node.rs diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 914e47fcf..d8da18cae 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -61,7 +61,7 @@ impl Client { // spawn the RPC server if config.rpc_conf.enabled { - rpc::start_server(&config.rpc_conf, &log); + rpc::start_server(&config.rpc_conf, beacon_chain.clone(), &log); } Ok(Client { diff --git a/beacon_node/rpc/Cargo.toml b/beacon_node/rpc/Cargo.toml index 4c3333ee1..acb68972c 100644 --- a/beacon_node/rpc/Cargo.toml +++ b/beacon_node/rpc/Cargo.toml @@ -7,7 +7,10 @@ edition = "2018" [dependencies] bls = { path = "../../eth2/utils/bls" } beacon_chain = { path = "../beacon_chain" } - +version = { path = "../version" } +types = { path = "../../eth2/types" } +ssz = { path = "../../eth2/utils/ssz" } +slot_clock = { path = "../../eth2/utils/slot_clock" } protos = { path = "../../protos" } grpcio = { version = "0.4", default-features = false, features = ["protobuf-codec"] } protobuf = "2.0.2" @@ -16,8 +19,5 @@ db = { path = "../db" } dirs = "1.0.3" futures = "0.1.23" slog = "^2.2.3" -slot_clock = { path = "../../eth2/utils/slot_clock" } slog-term = "^2.4.0" slog-async = "^2.3.0" -types = { path = "../../eth2/types" } -ssz = { path = "../../eth2/utils/ssz" } diff --git a/beacon_node/rpc/src/beacon_node.rs b/beacon_node/rpc/src/beacon_node.rs new file mode 100644 index 000000000..8dd7721fd --- /dev/null +++ b/beacon_node/rpc/src/beacon_node.rs @@ -0,0 +1,50 @@ +use beacon_chain::{db::ClientDB, fork_choice::ForkChoice, slot_clock::SlotClock, BeaconChain}; +use futures::Future; +use grpcio::{RpcContext, UnarySink}; +use protos::services::{Empty, Fork, NodeInfo}; +use protos::services_grpc::BeaconNodeService; +use slog::{debug, trace, warn}; +use std::sync::Arc; + +#[derive(Clone)] +pub struct BeaconNodeServiceInstance +where + T: ClientDB + Clone, + U: SlotClock + Clone, + F: ForkChoice + Clone, +{ + pub chain: Arc>, + pub log: slog::Logger, +} + +impl BeaconNodeService for BeaconNodeServiceInstance +where + T: ClientDB + Clone, + U: SlotClock + Clone, + F: ForkChoice + Clone, +{ + /// Provides basic node information. + fn info(&mut self, ctx: RpcContext, _req: Empty, sink: UnarySink) { + trace!(self.log, "Node info requested via RPC"); + + let mut node_info = NodeInfo::new(); + node_info.set_version(version::version()); + // get the chain state fork + let state_fork = self.chain.state.read().fork.clone(); + // build the rpc fork struct + let mut fork = Fork::new(); + fork.set_previous_version(state_fork.previous_version.to_vec()); + fork.set_current_version(state_fork.current_version.to_vec()); + fork.set_epoch(state_fork.epoch.into()); + node_info.set_fork(fork); + + node_info.set_chain_id(self.chain.spec.chain_id as u32); + + // send the node_info the requester + let error_log = self.log.clone(); + let f = sink + .success(node_info) + .map_err(move |e| warn!(error_log, "failed to reply {:?}", e)); + ctx.spawn(f) + } +} diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index 7f776d7d8..2f2abcc1f 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -1,20 +1,44 @@ mod beacon_block; +mod beacon_node; pub mod config; mod validator; use self::beacon_block::BeaconBlockServiceInstance; +use self::beacon_node::BeaconNodeServiceInstance; use self::validator::ValidatorServiceInstance; +use beacon_chain::{db::ClientDB, fork_choice::ForkChoice, slot_clock::SlotClock, BeaconChain}; pub use config::Config as RPCConfig; use grpcio::{Environment, Server, ServerBuilder}; -use protos::services_grpc::{create_beacon_block_service, create_validator_service}; +use protos::services_grpc::{ + create_beacon_block_service, create_beacon_node_service, create_validator_service, +}; use std::sync::Arc; use slog::{info, o}; -pub fn start_server(config: &RPCConfig, log: &slog::Logger) -> Server { +pub fn start_server( + config: &RPCConfig, + beacon_chain: Arc>, + log: &slog::Logger, +) -> Server +where + T: ClientDB + Clone + 'static, + U: SlotClock + Clone + 'static, + F: ForkChoice + Clone + 'static, +{ let log = log.new(o!("Service"=>"RPC")); let env = Arc::new(Environment::new(1)); + // build the individual rpc services + + let beacon_node_service = { + let instance = BeaconNodeServiceInstance { + chain: beacon_chain.clone(), + log: log.clone(), + }; + create_beacon_node_service(instance) + }; + let beacon_block_service = { let instance = BeaconBlockServiceInstance { log: log.clone() }; create_beacon_block_service(instance) From 858cf4f1f42b4c56a6fbdf71c44fbb06e2e5097a Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 13:51:17 +1100 Subject: [PATCH 13/34] Add beacon_chain trait for gRPC server --- beacon_node/rpc/src/beacon_chain.rs | 31 +++++++++++++++++++++++++++++ beacon_node/rpc/src/beacon_node.rs | 22 ++++++-------------- beacon_node/rpc/src/lib.rs | 14 +++++-------- 3 files changed, 42 insertions(+), 25 deletions(-) create mode 100644 beacon_node/rpc/src/beacon_chain.rs diff --git a/beacon_node/rpc/src/beacon_chain.rs b/beacon_node/rpc/src/beacon_chain.rs new file mode 100644 index 000000000..9b2681876 --- /dev/null +++ b/beacon_node/rpc/src/beacon_chain.rs @@ -0,0 +1,31 @@ +use beacon_chain::BeaconChain as RawBeaconChain; +use beacon_chain::{ + db::ClientDB, + fork_choice::ForkChoice, + parking_lot::RwLockReadGuard, + slot_clock::SlotClock, + types::{BeaconState, ChainSpec}, + CheckPoint, +}; + +/// The RPC's API to the beacon chain. +pub trait BeaconChain: Send + Sync { + fn get_spec(&self) -> &ChainSpec; + + fn get_state(&self) -> RwLockReadGuard; +} + +impl BeaconChain for RawBeaconChain +where + T: ClientDB + Sized, + U: SlotClock, + F: ForkChoice, +{ + fn get_spec(&self) -> &ChainSpec { + &self.spec + } + + fn get_state(&self) -> RwLockReadGuard { + self.state.read() + } +} diff --git a/beacon_node/rpc/src/beacon_node.rs b/beacon_node/rpc/src/beacon_node.rs index 8dd7721fd..5a542cb90 100644 --- a/beacon_node/rpc/src/beacon_node.rs +++ b/beacon_node/rpc/src/beacon_node.rs @@ -1,4 +1,4 @@ -use beacon_chain::{db::ClientDB, fork_choice::ForkChoice, slot_clock::SlotClock, BeaconChain}; +use crate::beacon_chain::BeaconChain; use futures::Future; use grpcio::{RpcContext, UnarySink}; use protos::services::{Empty, Fork, NodeInfo}; @@ -7,22 +7,12 @@ use slog::{debug, trace, warn}; use std::sync::Arc; #[derive(Clone)] -pub struct BeaconNodeServiceInstance -where - T: ClientDB + Clone, - U: SlotClock + Clone, - F: ForkChoice + Clone, -{ - pub chain: Arc>, +pub struct BeaconNodeServiceInstance { + pub chain: Arc, pub log: slog::Logger, } -impl BeaconNodeService for BeaconNodeServiceInstance -where - T: ClientDB + Clone, - U: SlotClock + Clone, - F: ForkChoice + Clone, -{ +impl BeaconNodeService for BeaconNodeServiceInstance { /// Provides basic node information. fn info(&mut self, ctx: RpcContext, _req: Empty, sink: UnarySink) { trace!(self.log, "Node info requested via RPC"); @@ -30,7 +20,7 @@ where let mut node_info = NodeInfo::new(); node_info.set_version(version::version()); // get the chain state fork - let state_fork = self.chain.state.read().fork.clone(); + let state_fork = self.chain.get_state().fork.clone(); // build the rpc fork struct let mut fork = Fork::new(); fork.set_previous_version(state_fork.previous_version.to_vec()); @@ -38,7 +28,7 @@ where fork.set_epoch(state_fork.epoch.into()); node_info.set_fork(fork); - node_info.set_chain_id(self.chain.spec.chain_id as u32); + node_info.set_chain_id(self.chain.get_spec().chain_id as u32); // send the node_info the requester let error_log = self.log.clone(); diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index 2f2abcc1f..4565abf7a 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -1,12 +1,13 @@ mod beacon_block; +pub mod beacon_chain; mod beacon_node; pub mod config; mod validator; use self::beacon_block::BeaconBlockServiceInstance; +use self::beacon_chain::BeaconChain; use self::beacon_node::BeaconNodeServiceInstance; use self::validator::ValidatorServiceInstance; -use beacon_chain::{db::ClientDB, fork_choice::ForkChoice, slot_clock::SlotClock, BeaconChain}; pub use config::Config as RPCConfig; use grpcio::{Environment, Server, ServerBuilder}; use protos::services_grpc::{ @@ -16,16 +17,11 @@ use std::sync::Arc; use slog::{info, o}; -pub fn start_server( +pub fn start_server( config: &RPCConfig, - beacon_chain: Arc>, + beacon_chain: Arc, log: &slog::Logger, -) -> Server -where - T: ClientDB + Clone + 'static, - U: SlotClock + Clone + 'static, - F: ForkChoice + Clone + 'static, -{ +) -> Server { let log = log.new(o!("Service"=>"RPC")); let env = Arc::new(Environment::new(1)); From a4cfe682726a035b8f4dce4d395074f98205e8f6 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 16:46:52 +1100 Subject: [PATCH 14/34] Thread beacon node RPC server --- beacon_node/client/src/lib.rs | 23 +++++++++++----------- beacon_node/rpc/Cargo.toml | 2 ++ beacon_node/rpc/src/lib.rs | 35 +++++++++++++++++++++++++--------- validator_client/src/config.rs | 2 +- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index d8da18cae..a033da87b 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -24,12 +24,8 @@ pub struct Client { beacon_chain: Arc>, /// Reference to the network service. pub network: Arc, - /// Future to stop and begin shutdown of the Client. - //TODO: Decide best way to handle shutdown - pub exit: exit_future::Exit, - /// The sending future to call to terminate the Client. - //TODO: Decide best way to handle shutdown - pub exit_signal: Signal, + /// Signal to terminate the RPC server. + pub rpc_exit_signal: Option, /// The clients logger. log: slog::Logger, /// Marker to pin the beacon chain generics. @@ -43,8 +39,6 @@ impl Client { log: slog::Logger, executor: &TaskExecutor, ) -> error::Result { - let (exit_signal, exit) = exit_future::signal(); - // generate a beacon chain let beacon_chain = TClientType::initialise_beacon_chain(&config); @@ -59,16 +53,23 @@ impl Client { network_logger, )?; + let mut rpc_exit_signal = None; // spawn the RPC server if config.rpc_conf.enabled { - rpc::start_server(&config.rpc_conf, beacon_chain.clone(), &log); + rpc_exit_signal = Some(rpc::start_server( + &config.rpc_conf, + executor, + beacon_chain.clone(), + &log, + )); } + println!("Here"); + Ok(Client { config, beacon_chain, - exit, - exit_signal, + rpc_exit_signal, log, network, phantom: PhantomData, diff --git a/beacon_node/rpc/Cargo.toml b/beacon_node/rpc/Cargo.toml index acb68972c..d405982db 100644 --- a/beacon_node/rpc/Cargo.toml +++ b/beacon_node/rpc/Cargo.toml @@ -21,3 +21,5 @@ futures = "0.1.23" slog = "^2.2.3" slog-term = "^2.4.0" slog-async = "^2.3.0" +tokio = "0.1.17" +exit-future = "0.1.4" diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index 4565abf7a..02e34781c 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -9,24 +9,28 @@ use self::beacon_chain::BeaconChain; use self::beacon_node::BeaconNodeServiceInstance; use self::validator::ValidatorServiceInstance; pub use config::Config as RPCConfig; +use futures::{future, Future}; use grpcio::{Environment, Server, ServerBuilder}; use protos::services_grpc::{ create_beacon_block_service, create_beacon_node_service, create_validator_service, }; +use slog::{info, o, warn}; use std::sync::Arc; - -use slog::{info, o}; +use tokio::runtime::TaskExecutor; pub fn start_server( config: &RPCConfig, + executor: &TaskExecutor, beacon_chain: Arc, log: &slog::Logger, -) -> Server { +) -> exit_future::Signal { let log = log.new(o!("Service"=>"RPC")); let env = Arc::new(Environment::new(1)); - // build the individual rpc services + // build a channel to kill the rpc server + let (rpc_exit_signal, rpc_exit) = exit_future::signal(); + // build the individual rpc services let beacon_node_service = { let instance = BeaconNodeServiceInstance { chain: beacon_chain.clone(), @@ -50,9 +54,22 @@ pub fn start_server( .bind(config.listen_address.to_string(), config.port) .build() .unwrap(); - server.start(); - for &(ref host, port) in server.bind_addrs() { - info!(log, "gRPC listening on {}:{}", host, port); - } - server + + let spawn_rpc = { + server.start(); + for &(ref host, port) in server.bind_addrs() { + info!(log, "gRPC listening on {}:{}", host, port); + } + rpc_exit.and_then(move |_| { + info!(log, "RPC Server shutting down"); + server + .shutdown() + .wait() + .map(|_| ()) + .map_err(|e| warn!(log, "RPC server failed to shutdown: {:?}", e))?; + Ok(()) + }) + }; + executor.spawn(spawn_rpc); + rpc_exit_signal } diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 68405ed2f..49e0a506f 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -21,7 +21,7 @@ impl ClientConfig { }; fs::create_dir_all(&data_dir) .unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir)); - let server = "localhost:50051".to_string(); + let server = "localhost:5051".to_string(); let spec = ChainSpec::foundation(); Self { data_dir, From 4990569f68f8940cf0cff907716094e18999839b Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 16:48:25 +1100 Subject: [PATCH 15/34] Add BeaconNodeInfo RPC to validator client --- validator_client/src/main.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 4664d5dc9..b4da737c8 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,15 +1,17 @@ -use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; -use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; use crate::attester_service::{AttestationGrpcClient, AttesterService}; +use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use crate::config::ClientConfig; +use crate::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; use attester::test_utils::EpochMap; use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer}; use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; +use protos::services::{Empty, NodeInfo}; use protos::services_grpc::{ - AttestationServiceClient, BeaconBlockServiceClient, ValidatorServiceClient, + AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient, + ValidatorServiceClient, }; use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; @@ -94,6 +96,13 @@ fn main() { "data_dir" => &config.data_dir.to_str(), "server" => &config.server); + // Beacon node gRPC beacon node endpoints. + let beacon_node_grpc_client = { + let env = Arc::new(EnvBuilder::new().build()); + let ch = ChannelBuilder::new(env).connect(&config.server); + Arc::new(BeaconNodeServiceClient::new(ch)) + }; + // Beacon node gRPC beacon block endpoints. let beacon_block_grpc_client = { let env = Arc::new(EnvBuilder::new().build()); @@ -115,12 +124,14 @@ fn main() { Arc::new(AttestationServiceClient::new(ch)) }; + // retrieve node information + let node_info = beacon_node_grpc_client.info(&Empty::new()); + + info!(log, "Beacon node info: {:?}", node_info); + // Spec let spec = Arc::new(config.spec.clone()); - // Clock for determining the present slot. - // TODO: this shouldn't be a static time, instead it should be pulled from the beacon node. - // https://github.com/sigp/lighthouse/issues/160 let genesis_time = 1_549_935_547; let slot_clock = { info!(log, "Genesis time"; "unix_epoch_seconds" => genesis_time); From a1a5f29caae53c76d9e17b6e333f4d57ba598a38 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 16:56:54 +1100 Subject: [PATCH 16/34] Fix registering of node service --- beacon_node/rpc/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index 02e34781c..3c89bda1f 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -51,6 +51,7 @@ pub fn start_server( let mut server = ServerBuilder::new(env) .register_service(beacon_block_service) .register_service(validator_service) + .register_service(beacon_node_service) .bind(config.listen_address.to_string(), config.port) .build() .unwrap(); From b2cd771a4203b07c3c19a3c914ed1016bab3edb6 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 17:04:55 +1100 Subject: [PATCH 17/34] Shift argument passing into config module --- validator_client/src/config.rs | 37 ++++++++++++++++++++++++++++++++++ validator_client/src/main.rs | 37 ++-------------------------------- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 49e0a506f..60edc564a 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,3 +1,5 @@ +use clap::ArgMatches; +use slog::{error, info}; use std::fs; use std::path::PathBuf; use types::ChainSpec; @@ -29,4 +31,39 @@ impl ClientConfig { spec, } } + + pub fn parse_args(matches: ArgMatches, log: &slog::Logger) -> Result { + let mut config = ClientConfig::default(); + // Custom datadir + if let Some(dir) = matches.value_of("datadir") { + config.data_dir = PathBuf::from(dir.to_string()); + } + + // Custom server port + if let Some(server_str) = matches.value_of("server") { + if let Ok(addr) = server_str.parse::() { + config.server = addr.to_string(); + } else { + error!(log, "Invalid address"; "server" => server_str); + return Err("Invalid address"); + } + } + + // TODO: Permit loading a custom spec from file. + // Custom spec + if let Some(spec_str) = matches.value_of("spec") { + match spec_str { + "foundation" => config.spec = ChainSpec::foundation(), + "few_validators" => config.spec = ChainSpec::few_validators(), + // Should be impossible due to clap's `possible_values(..)` function. + _ => unreachable!(), + }; + } + + // Log configuration + info!(log, ""; + "data_dir" => &config.data_dir.to_str(), + "server" => &config.server); + Ok(config) + } } diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index b4da737c8..c6cf586f3 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -13,12 +13,10 @@ use protos::services_grpc::{ AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient, ValidatorServiceClient, }; -use slog::{error, info, o, Drain}; +use slog::{info, o, Drain}; use slot_clock::SystemTimeSlotClock; -use std::path::PathBuf; use std::sync::Arc; use std::thread; -use types::ChainSpec; mod attester_service; mod block_producer_service; @@ -63,38 +61,7 @@ fn main() { ) .get_matches(); - let mut config = ClientConfig::default(); - - // Custom datadir - if let Some(dir) = matches.value_of("datadir") { - config.data_dir = PathBuf::from(dir.to_string()); - } - - // Custom server port - if let Some(server_str) = matches.value_of("server") { - if let Ok(addr) = server_str.parse::() { - config.server = addr.to_string(); - } else { - error!(log, "Invalid address"; "server" => server_str); - return; - } - } - - // TODO: Permit loading a custom spec from file. - // Custom spec - if let Some(spec_str) = matches.value_of("spec") { - match spec_str { - "foundation" => config.spec = ChainSpec::foundation(), - "few_validators" => config.spec = ChainSpec::few_validators(), - // Should be impossible due to clap's `possible_values(..)` function. - _ => unreachable!(), - }; - } - - // Log configuration - info!(log, ""; - "data_dir" => &config.data_dir.to_str(), - "server" => &config.server); + let config = ClientConfig::parse_args(matches, &log).unwrap(); // Beacon node gRPC beacon node endpoints. let beacon_node_grpc_client = { From c4454289d649e2be37b5e17931d706fe6aecfed2 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 17:27:07 +1100 Subject: [PATCH 18/34] Initial implementation of validator service --- validator_client/Cargo.toml | 2 +- validator_client/src/config.rs | 6 +- validator_client/src/main.rs | 166 ++------------------------------ validator_client/src/service.rs | 166 ++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 161 deletions(-) create mode 100644 validator_client/src/service.rs diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index cdde71774..4bd63715c 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "validator_client" version = "0.1.0" -authors = ["Paul Hauner "] +authors = ["Paul Hauner ", "Age Manning "] edition = "2018" [dependencies] diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 60edc564a..0bf320b4f 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -6,7 +6,7 @@ use types::ChainSpec; /// Stores the core configuration for this validator instance. #[derive(Clone)] -pub struct ClientConfig { +pub struct Config { pub data_dir: PathBuf, pub server: String, pub spec: ChainSpec, @@ -14,7 +14,7 @@ pub struct ClientConfig { const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse-validators"; -impl ClientConfig { +impl Config { /// Build a new configuration from defaults. pub fn default() -> Self { let data_dir = { @@ -33,7 +33,7 @@ impl ClientConfig { } pub fn parse_args(matches: ArgMatches, log: &slog::Logger) -> Result { - let mut config = ClientConfig::default(); + let mut config = Config::default(); // Custom datadir if let Some(dir) = matches.value_of("datadir") { config.data_dir = PathBuf::from(dir.to_string()); diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index c6cf586f3..0ec392731 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,27 +1,13 @@ -use crate::attester_service::{AttestationGrpcClient, AttesterService}; -use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; -use crate::config::ClientConfig; -use crate::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; -use attester::test_utils::EpochMap; -use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; -use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer}; -use bls::Keypair; -use clap::{App, Arg}; -use grpcio::{ChannelBuilder, EnvBuilder}; -use protos::services::{Empty, NodeInfo}; -use protos::services_grpc::{ - AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient, - ValidatorServiceClient, -}; -use slog::{info, o, Drain}; -use slot_clock::SystemTimeSlotClock; -use std::sync::Arc; -use std::thread; - mod attester_service; mod block_producer_service; mod config; mod duties; +mod service; + +use crate::config::Config as ValidatorConfig; +use clap::{App, Arg}; +use service::Service as ValidatorService; +use slog::{o, Drain}; fn main() { // Logging @@ -61,142 +47,8 @@ fn main() { ) .get_matches(); - let config = ClientConfig::parse_args(matches, &log).unwrap(); + let config = ValidatorConfig::parse_args(matches, &log).unwrap(); - // Beacon node gRPC beacon node endpoints. - let beacon_node_grpc_client = { - let env = Arc::new(EnvBuilder::new().build()); - let ch = ChannelBuilder::new(env).connect(&config.server); - Arc::new(BeaconNodeServiceClient::new(ch)) - }; - - // Beacon node gRPC beacon block endpoints. - let beacon_block_grpc_client = { - let env = Arc::new(EnvBuilder::new().build()); - let ch = ChannelBuilder::new(env).connect(&config.server); - Arc::new(BeaconBlockServiceClient::new(ch)) - }; - - // Beacon node gRPC validator endpoints. - let validator_grpc_client = { - let env = Arc::new(EnvBuilder::new().build()); - let ch = ChannelBuilder::new(env).connect(&config.server); - Arc::new(ValidatorServiceClient::new(ch)) - }; - - //Beacon node gRPC attester endpoints. - let attester_grpc_client = { - let env = Arc::new(EnvBuilder::new().build()); - let ch = ChannelBuilder::new(env).connect(&config.server); - Arc::new(AttestationServiceClient::new(ch)) - }; - - // retrieve node information - let node_info = beacon_node_grpc_client.info(&Empty::new()); - - info!(log, "Beacon node info: {:?}", node_info); - - // Spec - let spec = Arc::new(config.spec.clone()); - - let genesis_time = 1_549_935_547; - let slot_clock = { - info!(log, "Genesis time"; "unix_epoch_seconds" => genesis_time); - let clock = SystemTimeSlotClock::new(genesis_time, spec.seconds_per_slot) - .expect("Unable to instantiate SystemTimeSlotClock."); - Arc::new(clock) - }; - - let poll_interval_millis = spec.seconds_per_slot * 1000 / 10; // 10% epoch time precision. - info!(log, "Starting block producer service"; "polls_per_epoch" => spec.seconds_per_slot * 1000 / poll_interval_millis); - - /* - * Start threads. - */ - 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(EpochDutiesMap::new(spec.slots_per_epoch)); - let epoch_map_for_attester = Arc::new(EpochMap::new(spec.slots_per_epoch)); - - // Spawn a new thread to maintain the validator's `EpochDuties`. - let duties_manager_thread = { - let spec = spec.clone(); - let duties_map = duties_map.clone(); - let slot_clock = slot_clock.clone(); - let log = log.clone(); - let beacon_node = validator_grpc_client.clone(); - let pubkey = keypair.pk.clone(); - thread::spawn(move || { - let manager = DutiesManager { - duties_map, - pubkey, - spec, - slot_clock, - beacon_node, - }; - let mut duties_manager_service = DutiesManagerService { - manager, - poll_interval_millis, - log, - }; - - duties_manager_service.run(); - }) - }; - - // Spawn a new thread to perform block production for the validator. - let producer_thread = { - let spec = spec.clone(); - let signer = Arc::new(BlockProposerLocalSigner::new(keypair.clone())); - 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(); - }) - }; - - // Spawn a new thread for attestation for the validator. - let attester_thread = { - 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(); - }) - }; - - threads.push((duties_manager_thread, producer_thread, attester_thread)); - } - - // Naively wait for all the threads to complete. - for tuple in threads { - let (manager, producer, attester) = tuple; - let _ = producer.join(); - let _ = manager.join(); - let _ = attester.join(); - } + // start the validator service. + ValidatorService::start(config, log); } diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs new file mode 100644 index 000000000..95f2a2361 --- /dev/null +++ b/validator_client/src/service.rs @@ -0,0 +1,166 @@ +/// The validator service. Connects to a beacon node and signs blocks when required. +use crate::attester_service::{AttestationGrpcClient, AttesterService}; +use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; +use crate::config::Config as ValidatorConfig; +use crate::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; +use attester::test_utils::EpochMap; +use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; +use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer}; +use bls::Keypair; +use grpcio::{ChannelBuilder, EnvBuilder}; +use protos::services::{Empty, NodeInfo}; +use protos::services_grpc::{ + AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient, + ValidatorServiceClient, +}; +use slog::{info, o, Drain}; +use slot_clock::SystemTimeSlotClock; +use std::sync::Arc; +use std::thread; + +/// The validator service. This is the main thread that executes and maintains validator +/// duties. +pub struct Service {} + +impl Service { + pub fn start(config: ValidatorConfig, log: slog::Logger) { + // initialize the RPC clients + + let env = Arc::new(EnvBuilder::new().build()); + // Beacon node gRPC beacon node endpoints. + let beacon_node_grpc_client = { + let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + Arc::new(BeaconNodeServiceClient::new(ch)) + }; + + // Beacon node gRPC beacon block endpoints. + let beacon_block_grpc_client = { + let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + Arc::new(BeaconBlockServiceClient::new(ch)) + }; + + // Beacon node gRPC validator endpoints. + let validator_grpc_client = { + let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + Arc::new(ValidatorServiceClient::new(ch)) + }; + + //Beacon node gRPC attester endpoints. + let attester_grpc_client = { + let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + Arc::new(AttestationServiceClient::new(ch)) + }; + + // connect to the node and retrieve its properties + // node_info = connect_to_node(beacon_ndoe_grpc_client); + + // retrieve node information + let node_info = beacon_node_grpc_client.info(&Empty::new()); + + info!(log, "Beacon node info: {:?}", node_info); + + // Spec + let spec = Arc::new(config.spec.clone()); + + let genesis_time = 1_549_935_547; + let slot_clock = { + info!(log, "Genesis time"; "unix_epoch_seconds" => genesis_time); + let clock = SystemTimeSlotClock::new(genesis_time, spec.seconds_per_slot) + .expect("Unable to instantiate SystemTimeSlotClock."); + Arc::new(clock) + }; + + let poll_interval_millis = spec.seconds_per_slot * 1000 / 10; // 10% epoch time precision. + info!(log, "Starting block producer service"; "polls_per_epoch" => spec.seconds_per_slot * 1000 / poll_interval_millis); + + /* + * Start threads. + */ + 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(EpochDutiesMap::new(spec.slots_per_epoch)); + let epoch_map_for_attester = Arc::new(EpochMap::new(spec.slots_per_epoch)); + + // Spawn a new thread to maintain the validator's `EpochDuties`. + let duties_manager_thread = { + let spec = spec.clone(); + let duties_map = duties_map.clone(); + let slot_clock = slot_clock.clone(); + let log = log.clone(); + let beacon_node = validator_grpc_client.clone(); + let pubkey = keypair.pk.clone(); + thread::spawn(move || { + let manager = DutiesManager { + duties_map, + pubkey, + spec, + slot_clock, + beacon_node, + }; + let mut duties_manager_service = DutiesManagerService { + manager, + poll_interval_millis, + log, + }; + + duties_manager_service.run(); + }) + }; + + // Spawn a new thread to perform block production for the validator. + let producer_thread = { + let spec = spec.clone(); + let signer = Arc::new(BlockProposerLocalSigner::new(keypair.clone())); + 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(); + }) + }; + + // Spawn a new thread for attestation for the validator. + let attester_thread = { + 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(); + }) + }; + + threads.push((duties_manager_thread, producer_thread, attester_thread)); + } + + // Naively wait for all the threads to complete. + for tuple in threads { + let (manager, producer, attester) = tuple; + let _ = producer.join(); + let _ = manager.join(); + let _ = attester.join(); + } + } +} From 61fc946d54a0de8b937152bf2d799c428ed7b29a Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 22:50:16 +1100 Subject: [PATCH 19/34] Adds initial connection to beacon node with retries --- validator_client/src/service.rs | 79 +++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 95f2a2361..3bf2d6104 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -13,16 +13,75 @@ use protos::services_grpc::{ AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient, ValidatorServiceClient, }; -use slog::{info, o, Drain}; +use slog::{info, o, warn, Drain}; use slot_clock::SystemTimeSlotClock; use std::sync::Arc; use std::thread; +use std::time::Duration; +use types::{Epoch, Fork}; /// The validator service. This is the main thread that executes and maintains validator /// duties. -pub struct Service {} +#[derive(Debug)] +pub struct Service { + /// The node we currently connected to. + connected_node_version: String, + /// The chain id we are processing on. + chain_id: u16, + /// The fork state we processing on. + fork: Fork, + // /// The slot clock keeping track of time. + // slot_clock: Arc, +} impl Service { + /// Initial connection to the beacon node to determine its properties. + fn connect_to_node( + node_client: Arc, + seconds_per_slot: u64, + log: &slog::Logger, + ) -> Self { + // retrieve node information + let node_info = loop { + let info = match node_client.info(&Empty::new()) { + Err(e) => { + warn!(log, "Could not connect to node. Error: {}", e); + info!(log, "Retrying in 5 seconds..."); + std::thread::sleep(Duration::from_secs(5)); + continue; + } + Ok(info) => break info, + }; + }; + + info!(log,"Beacon node connected"; "Node Version:" => node_info.version.clone(), "Chain ID:" => node_info.chain_id); + + let proto_fork = node_info.get_fork(); + let mut previous_version: [u8; 4] = [0; 4]; + let mut current_version: [u8; 4] = [0; 4]; + previous_version.copy_from_slice(&proto_fork.get_previous_version()[..4]); + current_version.copy_from_slice(&proto_fork.get_current_version()[..4]); + let fork = Fork { + previous_version, + current_version, + epoch: Epoch::from(proto_fork.get_epoch()), + }; + + let genesis_time = 1_549_935_547; + let slot_clock = { + info!(log, "Genesis time"; "unix_epoch_seconds" => genesis_time); + let clock = SystemTimeSlotClock::new(genesis_time, seconds_per_slot) + .expect("Unable to instantiate SystemTimeSlotClock."); + Arc::new(clock) + }; + + Self { + connected_node_version: node_info.version, + chain_id: node_info.chain_id as u16, + fork, + } + } + pub fn start(config: ValidatorConfig, log: slog::Logger) { // initialize the RPC clients @@ -51,16 +110,13 @@ impl Service { Arc::new(AttestationServiceClient::new(ch)) }; + let spec = Arc::new(config.spec); // connect to the node and retrieve its properties - // node_info = connect_to_node(beacon_ndoe_grpc_client); + let service = + Service::connect_to_node(beacon_node_grpc_client, spec.seconds_per_slot, &log); - // retrieve node information - let node_info = beacon_node_grpc_client.info(&Empty::new()); - - info!(log, "Beacon node info: {:?}", node_info); - - // Spec - let spec = Arc::new(config.spec.clone()); + let poll_interval_millis = spec.seconds_per_slot * 1000 / 10; // 10% epoch time precision. + info!(log, "Starting block producer service"; "polls_per_epoch" => spec.seconds_per_slot * 1000 / poll_interval_millis); let genesis_time = 1_549_935_547; let slot_clock = { @@ -70,9 +126,6 @@ impl Service { Arc::new(clock) }; - let poll_interval_millis = spec.seconds_per_slot * 1000 / 10; // 10% epoch time precision. - info!(log, "Starting block producer service"; "polls_per_epoch" => spec.seconds_per_slot * 1000 / poll_interval_millis); - /* * Start threads. */ From 17cd5bb991985ab9f3bebfd6e8614872f34d27de Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 23:01:10 +1100 Subject: [PATCH 20/34] Adds genesis time to node info. Closes #256 --- beacon_node/rpc/src/beacon_node.rs | 14 ++++++++++---- protos/src/services.proto | 2 +- validator_client/src/service.rs | 14 ++++++++------ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/beacon_node/rpc/src/beacon_node.rs b/beacon_node/rpc/src/beacon_node.rs index 5a542cb90..7b00f04f4 100644 --- a/beacon_node/rpc/src/beacon_node.rs +++ b/beacon_node/rpc/src/beacon_node.rs @@ -3,7 +3,7 @@ use futures::Future; use grpcio::{RpcContext, UnarySink}; use protos::services::{Empty, Fork, NodeInfo}; use protos::services_grpc::BeaconNodeService; -use slog::{debug, trace, warn}; +use slog::{trace, warn}; use std::sync::Arc; #[derive(Clone)] @@ -17,17 +17,23 @@ impl BeaconNodeService for BeaconNodeServiceInstance { fn info(&mut self, ctx: RpcContext, _req: Empty, sink: UnarySink) { trace!(self.log, "Node info requested via RPC"); + // build the response let mut node_info = NodeInfo::new(); node_info.set_version(version::version()); - // get the chain state fork - let state_fork = self.chain.get_state().fork.clone(); + + // get the chain state + let state = self.chain.get_state(); + let state_fork = state.fork.clone(); + let genesis_time = state.genesis_time.clone(); + // build the rpc fork struct let mut fork = Fork::new(); fork.set_previous_version(state_fork.previous_version.to_vec()); fork.set_current_version(state_fork.current_version.to_vec()); fork.set_epoch(state_fork.epoch.into()); - node_info.set_fork(fork); + node_info.set_fork(fork); + node_info.set_genesis_time(genesis_time); node_info.set_chain_id(self.chain.get_spec().chain_id as u32); // send the node_info the requester diff --git a/protos/src/services.proto b/protos/src/services.proto index 45fcfb120..fbcde922d 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -44,6 +44,7 @@ message NodeInfo { string version = 1; Fork fork = 2; uint32 chain_id = 3; + uint64 genesis_time = 4; } message Fork { @@ -56,7 +57,6 @@ message Empty { } - /* * Block Production Service Messages */ diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 3bf2d6104..df3f64027 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -22,7 +22,6 @@ use types::{Epoch, Fork}; /// The validator service. This is the main thread that executes and maintains validator /// duties. -#[derive(Debug)] pub struct Service { /// The node we currently connected to. connected_node_version: String, @@ -30,8 +29,8 @@ pub struct Service { chain_id: u16, /// The fork state we processing on. fork: Fork, - // /// The slot clock keeping track of time. - // slot_clock: Arc, + /// The slot clock keeping track of time. + slot_clock: Arc, } impl Service { @@ -54,7 +53,10 @@ impl Service { }; }; - info!(log,"Beacon node connected"; "Node Version:" => node_info.version.clone(), "Chain ID:" => node_info.chain_id); + // build requisite objects to form Self + let genesis_time = node_info.get_genesis_time(); + + info!(log,"Beacon node connected"; "Node Version" => node_info.version.clone(), "Chain ID" => node_info.chain_id, "Genesis time" => genesis_time); let proto_fork = node_info.get_fork(); let mut previous_version: [u8; 4] = [0; 4]; @@ -67,9 +69,8 @@ impl Service { epoch: Epoch::from(proto_fork.get_epoch()), }; - let genesis_time = 1_549_935_547; + // build the validator slot clock let slot_clock = { - info!(log, "Genesis time"; "unix_epoch_seconds" => genesis_time); let clock = SystemTimeSlotClock::new(genesis_time, seconds_per_slot) .expect("Unable to instantiate SystemTimeSlotClock."); Arc::new(clock) @@ -79,6 +80,7 @@ impl Service { connected_node_version: node_info.version, chain_id: node_info.chain_id as u16, fork, + slot_clock, } } From 547a750d78443eea0745b9661980ba3b7950a4f2 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 22 Mar 2019 23:21:26 +1100 Subject: [PATCH 21/34] Setup basic structure before tokio runtime addition --- validator_client/src/service.rs | 119 +++++++++++++++++--------------- 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index df3f64027..bf3003834 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -31,18 +31,44 @@ pub struct Service { fork: Fork, /// The slot clock keeping track of time. slot_clock: Arc, + // GRPC Clients + /// The beacon block GRPC client. + beacon_block_client: Arc, + /// The validator GRPC client. + validator_client: Arc, + /// The attester GRPC client. + attester_client: Arc, + /// The validator client logger. + log: slog::Logger, } impl Service { + /// Initialise the service then run the core thread. + pub fn start(config: ValidatorConfig, log: slog::Logger) { + // connect to the node and retrieve its properties and initialize the gRPC clients + let service = Service::initialize_service(&config, log); + + // we have connected to a node and established its parameters. Spin up the core service + service.run(config); + } + /// Initial connection to the beacon node to determine its properties. - fn connect_to_node( - node_client: Arc, - seconds_per_slot: u64, - log: &slog::Logger, - ) -> Self { + /// + /// This tries to connect to a beacon node. Once connected, it initialised the gRPC clients + /// and returns an instance of the service. + fn initialize_service(config: &ValidatorConfig, log: slog::Logger) -> Self { + // initialise the beacon node client to check for a connection + + let env = Arc::new(EnvBuilder::new().build()); + // Beacon node gRPC beacon node endpoints. + let beacon_node_client = { + let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + Arc::new(BeaconNodeServiceClient::new(ch)) + }; + // retrieve node information let node_info = loop { - let info = match node_client.info(&Empty::new()) { + let info = match beacon_node_client.info(&Empty::new()) { Err(e) => { warn!(log, "Could not connect to node. Error: {}", e); info!(log, "Retrying in 5 seconds..."); @@ -71,63 +97,44 @@ impl Service { // build the validator slot clock let slot_clock = { - let clock = SystemTimeSlotClock::new(genesis_time, seconds_per_slot) + let clock = SystemTimeSlotClock::new(genesis_time, config.spec.seconds_per_slot) .expect("Unable to instantiate SystemTimeSlotClock."); Arc::new(clock) }; + // initialize the RPC clients + + // Beacon node gRPC beacon block endpoints. + let beacon_block_client = { + let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + Arc::new(BeaconBlockServiceClient::new(ch)) + }; + + // Beacon node gRPC validator endpoints. + let validator_client = { + let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + Arc::new(ValidatorServiceClient::new(ch)) + }; + + //Beacon node gRPC attester endpoints. + let attester_client = { + let ch = ChannelBuilder::new(env.clone()).connect(&config.server); + Arc::new(AttestationServiceClient::new(ch)) + }; + Self { connected_node_version: node_info.version, chain_id: node_info.chain_id as u16, fork, slot_clock, + beacon_block_client, + validator_client, + attester_client, + log, } } - pub fn start(config: ValidatorConfig, log: slog::Logger) { - // initialize the RPC clients - - let env = Arc::new(EnvBuilder::new().build()); - // Beacon node gRPC beacon node endpoints. - let beacon_node_grpc_client = { - let ch = ChannelBuilder::new(env.clone()).connect(&config.server); - Arc::new(BeaconNodeServiceClient::new(ch)) - }; - - // Beacon node gRPC beacon block endpoints. - let beacon_block_grpc_client = { - let ch = ChannelBuilder::new(env.clone()).connect(&config.server); - Arc::new(BeaconBlockServiceClient::new(ch)) - }; - - // Beacon node gRPC validator endpoints. - let validator_grpc_client = { - let ch = ChannelBuilder::new(env.clone()).connect(&config.server); - Arc::new(ValidatorServiceClient::new(ch)) - }; - - //Beacon node gRPC attester endpoints. - let attester_grpc_client = { - let ch = ChannelBuilder::new(env.clone()).connect(&config.server); - Arc::new(AttestationServiceClient::new(ch)) - }; - - let spec = Arc::new(config.spec); - // connect to the node and retrieve its properties - let service = - Service::connect_to_node(beacon_node_grpc_client, spec.seconds_per_slot, &log); - - let poll_interval_millis = spec.seconds_per_slot * 1000 / 10; // 10% epoch time precision. - info!(log, "Starting block producer service"; "polls_per_epoch" => spec.seconds_per_slot * 1000 / poll_interval_millis); - - let genesis_time = 1_549_935_547; - let slot_clock = { - info!(log, "Genesis time"; "unix_epoch_seconds" => genesis_time); - let clock = SystemTimeSlotClock::new(genesis_time, spec.seconds_per_slot) - .expect("Unable to instantiate SystemTimeSlotClock."); - Arc::new(clock) - }; - + fn run(&mut self, config: ValidatorConfig) { /* * Start threads. */ @@ -136,8 +143,10 @@ impl Service { // https://github.com/sigp/lighthouse/issues/160 let keypairs = vec![Keypair::random()]; + let spec = config.spec; + for keypair in keypairs { - info!(log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); + info!(self.log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); let duties_map = Arc::new(EpochDutiesMap::new(spec.slots_per_epoch)); let epoch_map_for_attester = Arc::new(EpochMap::new(spec.slots_per_epoch)); @@ -145,9 +154,9 @@ impl Service { let duties_manager_thread = { let spec = spec.clone(); let duties_map = duties_map.clone(); - let slot_clock = slot_clock.clone(); - let log = log.clone(); - let beacon_node = validator_grpc_client.clone(); + let slot_clock = self.slot_clock.clone(); + let log = self.log.clone(); + let beacon_node = self.validator_client.clone(); let pubkey = keypair.pk.clone(); thread::spawn(move || { let manager = DutiesManager { From 318d6a976e1d2e0100cdd72098e2311d313f07cf Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 23 Mar 2019 00:36:48 +1100 Subject: [PATCH 22/34] Initial tokio timer interval --- validator_client/src/service.rs | 50 +++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index bf3003834..0934a5a16 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -31,6 +31,8 @@ pub struct Service { fork: Fork, /// The slot clock keeping track of time. slot_clock: Arc, + /// The current slot we are processing. + current_slot: Slot, // GRPC Clients /// The beacon block GRPC client. beacon_block_client: Arc, @@ -122,11 +124,14 @@ impl Service { Arc::new(AttestationServiceClient::new(ch)) }; + let current_slot = slot_clock.present_slot().saturating_sub(1); + Self { connected_node_version: node_info.version, chain_id: node_info.chain_id as u16, fork, slot_clock, + current_slot, beacon_block_client, validator_client, attester_client, @@ -135,15 +140,50 @@ impl Service { } fn run(&mut self, config: ValidatorConfig) { - /* - * Start threads. - */ - let mut threads = vec![]; + + // 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 = vec![Keypair::random()]; - let spec = config.spec; + // set up the validator service runtime + let runtime = Builder::new().clock(Clock::system()).name_prefix("validator-client-").build().unwrap(); + + // set up the validator work interval - start at next slot and proceed every slot + let interval = { + let time_to_next_slot = { + let syslot_time = SystemTime::now(); + let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH)?; + let mut secs_to_slot = None; + if let Some(duration_since_genesis) = + duration_since_epoch.checked_sub(Duration::from_secs(self.genesis_seconds)) { + // seconds till next slot + secs_to_slot =duration_since_genesis.as_secs().checked_rem(config.spec.seconds_per_slot); + } + secs_to_slot.ok_or_else(0) + } + // Set the interval to start at the next slot, and every slot after + let slot_duration = Duration::from_secs(config.spec.seconds_per_slot); + //TODO: Handle checked add correctly + Interval::new(Instant::now().checked_add(secs_to_slot)?, slot_duration) + } + + // kick off core service + runtime.spawn(interval.for_each(|_| {})); + + + + let duties_map = Arc::new(EpochDutiesMap::new(spec.slots_per_epoch)); + let epoch_map_for_attester = Arc::new(EpochMap::new(spec.slots_per_epoch)); + let manager = DutiesManager { + duties_map, + pubkey, + spec, + slot_clock, + beacon_node, + }; + for keypair in keypairs { info!(self.log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); From 56d33d2e26866e6ebdd02835bb75b5bb3c5a2945 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 23 Mar 2019 11:48:36 +1100 Subject: [PATCH 23/34] Basic tokio slot stream implementation --- validator_client/Cargo.toml | 4 +- validator_client/src/duties/mod.rs | 13 +- validator_client/src/service.rs | 291 ++++++++++++++++------------- 3 files changed, 170 insertions(+), 138 deletions(-) diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 4bd63715c..e8cff2622 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" block_proposer = { path = "../eth2/block_proposer" } attester = { path = "../eth2/attester" } bls = { path = "../eth2/utils/bls" } +ssz = { path = "../eth2/utils/ssz" } clap = "2.32.0" dirs = "1.0.3" grpcio = { version = "0.4", default-features = false, features = ["protobuf-codec"] } @@ -18,4 +19,5 @@ types = { path = "../eth2/types" } slog = "^2.2.3" slog-term = "^2.4.0" slog-async = "^2.3.0" -ssz = { path = "../eth2/utils/ssz" } +tokio = "0.1.18" +tokio-timer = "0.2.10" diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 29bd81d0a..c2b95b1c5 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -39,11 +39,11 @@ pub enum Error { /// A polling state machine which ensures the latest `EpochDuties` are obtained from the Beacon /// Node. /// -/// There is a single `DutiesManager` per validator instance. +/// This keeps track of all validator keys and required voting slots. pub struct DutiesManager { pub duties_map: Arc, - /// The validator's public key. - pub pubkey: PublicKey, + /// A list of all public keys known to the validator service. + pub pubkeys: Vec, pub spec: Arc, pub slot_clock: Arc, pub beacon_node: Arc, @@ -54,6 +54,8 @@ impl DutiesManager { /// /// 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.). + //TODO: Remove the poll and trust the tokio system-clock timer. Leave for now to ensure the + //timer is accurate. pub fn poll(&self) -> Result { let slot = self .slot_clock @@ -63,7 +65,10 @@ impl DutiesManager { let epoch = slot.epoch(self.spec.slots_per_epoch); - if let Some(duties) = self.beacon_node.request_shuffling(epoch, &self.pubkey)? { + if let Some(duties) = self + .beacon_node + .request_shuffling(epoch, &self.pubkeys[0])? + { // If these duties were known, check to see if they're updates or identical. let result = if let Some(known_duties) = self.duties_map.get(epoch)? { if known_duties == duties { diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 0934a5a16..322c370b5 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -8,17 +8,23 @@ use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer}; use bls::Keypair; use grpcio::{ChannelBuilder, EnvBuilder}; -use protos::services::{Empty, NodeInfo}; +use protos::services::Empty; use protos::services_grpc::{ AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient, ValidatorServiceClient, }; -use slog::{info, o, warn, Drain}; -use slot_clock::SystemTimeSlotClock; +use slog::{debug, info, warn}; +use slot_clock::{SlotClock, SystemTimeSlotClock}; +use std::ops::Sub; use std::sync::Arc; -use std::thread; -use std::time::Duration; -use types::{Epoch, Fork}; +use std::time::{Duration, Instant, SystemTime}; +use tokio::prelude::*; +use tokio::runtime::Builder; +use tokio::timer::Interval; +use tokio_timer::clock::Clock; +use types::{Epoch, Fork, Slot}; + +//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 /// duties. @@ -33,6 +39,8 @@ pub struct Service { slot_clock: Arc, /// The current slot we are processing. current_slot: Slot, + /// Seconds until the next slot. This is used for initializing the tokio timer interval. + seconds_to_next_slot: Duration, // GRPC Clients /// The beacon block GRPC client. beacon_block_client: Arc, @@ -45,15 +53,6 @@ pub struct Service { } impl Service { - /// Initialise the service then run the core thread. - pub fn start(config: ValidatorConfig, log: slog::Logger) { - // connect to the node and retrieve its properties and initialize the gRPC clients - let service = Service::initialize_service(&config, log); - - // we have connected to a node and established its parameters. Spin up the core service - service.run(config); - } - /// 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 @@ -124,7 +123,24 @@ impl Service { Arc::new(AttestationServiceClient::new(ch)) }; - let current_slot = slot_clock.present_slot().saturating_sub(1); + //TODO: Add error chain. Handle errors + let current_slot = slot_clock.present_slot().unwrap().unwrap().sub(1); + + // calculate seconds to the next slot + let seconds_to_next_slot = { + let syslot_time = SystemTime::now(); + let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let mut secs_to_slot = None; + if let Some(duration_since_genesis) = + duration_since_epoch.checked_sub(Duration::from_secs(genesis_time)) + { + // seconds till next slot + secs_to_slot = duration_since_genesis + .as_secs() + .checked_rem(config.spec.seconds_per_slot); + } + secs_to_slot.unwrap_or_else(|| 0) + }; Self { connected_node_version: node_info.version, @@ -132,6 +148,7 @@ impl Service { fork, slot_clock, current_slot, + seconds_to_next_slot: Duration::from_secs(seconds_to_next_slot), beacon_block_client, validator_client, attester_client, @@ -139,132 +156,140 @@ impl Service { } } - fn run(&mut self, config: ValidatorConfig) { + /// Initialise the service then run the core thread. + pub fn start(config: ValidatorConfig, log: slog::Logger) { + // connect to the node and retrieve its properties and initialize the gRPC clients + let service = Service::initialize_service(&config, log); + + // we have connected to a node and established its parameters. Spin up the core service + + // set up the validator service runtime + let mut runtime = Builder::new() + .clock(Clock::system()) + .name_prefix("validator-client-") + .build() + .unwrap(); + + // set up the validator work interval - start at next slot and proceed every slot + // TODO: Error chain handle errors. + let interval = { + // Set the interval to start at the next slot, and every slot after + let slot_duration = Duration::from_secs(config.spec.seconds_per_slot); + //TODO: Handle checked add correctly + Interval::new(Instant::now() + service.seconds_to_next_slot, slot_duration) + }; + + // kick off 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 = vec![Keypair::random()]; + let keypairs = Arc::new(vec![Keypair::random()]); - // set up the validator service runtime - let runtime = Builder::new().clock(Clock::system()).name_prefix("validator-client-").build().unwrap(); - - // set up the validator work interval - start at next slot and proceed every slot - let interval = { - let time_to_next_slot = { - let syslot_time = SystemTime::now(); - let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH)?; - let mut secs_to_slot = None; - if let Some(duration_since_genesis) = - duration_since_epoch.checked_sub(Duration::from_secs(self.genesis_seconds)) { - // seconds till next slot - secs_to_slot =duration_since_genesis.as_secs().checked_rem(config.spec.seconds_per_slot); - } - secs_to_slot.ok_or_else(0) - } - // Set the interval to start at the next slot, and every slot after - let slot_duration = Duration::from_secs(config.spec.seconds_per_slot); - //TODO: Handle checked add correctly - Interval::new(Instant::now().checked_add(secs_to_slot)?, slot_duration) - } - - // kick off core service - runtime.spawn(interval.for_each(|_| {})); - - - - let duties_map = Arc::new(EpochDutiesMap::new(spec.slots_per_epoch)); - let epoch_map_for_attester = Arc::new(EpochMap::new(spec.slots_per_epoch)); + // build requisite objects to pass to core thread. + let duties_map = Arc::new(EpochDutiesMap::new(config.spec.slots_per_epoch)); + let epoch_map_for_attester = Arc::new(EpochMap::new(config.spec.slots_per_epoch)); let manager = DutiesManager { duties_map, - pubkey, - spec, - slot_clock, - beacon_node, + pubkeys: keypairs.iter().map(|keypair| keypair.pk.clone()).collect(), + spec: Arc::new(config.spec), + slot_clock: service.slot_clock.clone(), + beacon_node: service.validator_client.clone(), }; - - for keypair in keypairs { - info!(self.log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); - let duties_map = Arc::new(EpochDutiesMap::new(spec.slots_per_epoch)); - let epoch_map_for_attester = Arc::new(EpochMap::new(spec.slots_per_epoch)); - - // Spawn a new thread to maintain the validator's `EpochDuties`. - let duties_manager_thread = { - let spec = spec.clone(); - let duties_map = duties_map.clone(); - let slot_clock = self.slot_clock.clone(); - let log = self.log.clone(); - let beacon_node = self.validator_client.clone(); - let pubkey = keypair.pk.clone(); - thread::spawn(move || { - let manager = DutiesManager { - duties_map, - pubkey, - spec, - slot_clock, - beacon_node, - }; - let mut duties_manager_service = DutiesManagerService { - manager, - poll_interval_millis, - log, - }; - - duties_manager_service.run(); - }) - }; - - // Spawn a new thread to perform block production for the validator. - let producer_thread = { - let spec = spec.clone(); - let signer = Arc::new(BlockProposerLocalSigner::new(keypair.clone())); - 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(); - }) - }; - - // Spawn a new thread for attestation for the validator. - let attester_thread = { - 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(); - }) - }; - - threads.push((duties_manager_thread, producer_thread, attester_thread)); - } - - // Naively wait for all the threads to complete. - for tuple in threads { - let (manager, producer, attester) = tuple; - let _ = producer.join(); - let _ = manager.join(); - let _ = attester.join(); - } + runtime.block_on(interval.for_each(move |_| { + // update duties + debug!(service.log, "Processing new slot..."); + manager.poll(); + Ok(()) + })); } + + /* + + let duties_map = Arc::new(EpochDutiesMap::new(spec.slots_per_epoch)); + let epoch_map_for_attester = Arc::new(EpochMap::new(spec.slots_per_epoch)); + + + for keypair in keypairs { + info!(self.log, "Starting validator services"; "validator" => keypair.pk.concatenated_hex_id()); + + // Spawn a new thread to maintain the validator's `EpochDuties`. + let duties_manager_thread = { + let spec = spec.clone(); + let duties_map = duties_map.clone(); + let slot_clock = self.slot_clock.clone(); + let log = self.log.clone(); + let beacon_node = self.validator_client.clone(); + let pubkey = keypair.pk.clone(); + thread::spawn(move || { + let manager = DutiesManager { + duties_map, + pubkey, + spec, + slot_clock, + beacon_node, + }; + let mut duties_manager_service = DutiesManagerService { + manager, + poll_interval_millis, + log, + }; + + duties_manager_service.run(); + }) + }; + + // Spawn a new thread to perform block production for the validator. + let producer_thread = { + let spec = spec.clone(); + let signer = Arc::new(BlockProposerLocalSigner::new(keypair.clone())); + 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(); + }) + }; + + // Spawn a new thread for attestation for the validator. + let attester_thread = { + 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(); + }) + }; + + threads.push((duties_manager_thread, producer_thread, attester_thread)); + } + + // Naively wait for all the threads to complete. + for tuple in threads { + let (manager, producer, attester) = tuple; + let _ = producer.join(); + let _ = manager.join(); + let _ = attester.join(); + } + */ } From fba916a0d8530b491d9d13ff01fff59f5b6a8df3 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Sat, 23 Mar 2019 15:46:51 +1100 Subject: [PATCH 24/34] Updated validator config according to suggestions. - Directory structure changed slightly - Uses a filter_map instead of a for loop. - All errors reading files does not prevent others from being read. - The accounts manager needs to generate files first, with the same structure. --- account_manager/src/main.rs | 4 +- validator_client/README.md | 1 - validator_client/src/config.rs | 172 +++++++++++++++++---------------- validator_client/src/lib.rs | 2 +- validator_client/src/main.rs | 19 ++-- 5 files changed, 100 insertions(+), 98 deletions(-) diff --git a/account_manager/src/main.rs b/account_manager/src/main.rs index 03546c95b..42c78aaea 100644 --- a/account_manager/src/main.rs +++ b/account_manager/src/main.rs @@ -2,7 +2,7 @@ use bls::Keypair; use clap::{App, Arg, SubCommand}; use slog::{debug, info, o, Drain}; use std::path::PathBuf; -use validator_client::config::ValidatorClientConfig; +use validator_client::Config as ValidatorClientConfig; fn main() { // Logging @@ -31,7 +31,7 @@ fn main() { ) .get_matches(); - let config = ValidatorClientConfig::build_config(&matches) + let config = ValidatorClientConfig::parse_args(&matches, &log) .expect("Unable to build a configuration for the account manager."); // Log configuration diff --git a/validator_client/README.md b/validator_client/README.md index 109d9f317..03979fbb8 100644 --- a/validator_client/README.md +++ b/validator_client/README.md @@ -65,7 +65,6 @@ with `--datadir`. The configuration directory structure looks like: ``` ~/.lighthouse-validator -└── validators ├── 3cf4210d58ec │   └── private.key ├── 9b5d8b5be4e7 diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index d056136c9..3ca066c89 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -5,119 +5,129 @@ use std::fs; use std::fs::File; use std::io::{Error, ErrorKind}; use std::path::PathBuf; +use slog::{debug, info, error}; use types::ChainSpec; /// Stores the core configuration for this validator instance. #[derive(Clone)] -pub struct ValidatorClientConfig { +pub struct Config { /// The data directory, which stores all validator databases pub data_dir: PathBuf, - /// The directory where the individual validator configuration directories are stored. - pub validator_dir: PathBuf, /// The server at which the Beacon Node can be contacted pub server: String, /// The chain specification that we are connecting to pub spec: ChainSpec, } -const DEFAULT_VALIDATOR_DATADIR: &str = ".lighthouse-validator"; -const DEFAULT_VALIDATORS_SUBDIR: &str = "validators"; const DEFAULT_PRIVATE_KEY_FILENAME: &str = "private.key"; -impl ValidatorClientConfig { - /// Build a new configuration from defaults, which are overrided by arguments provided. - pub fn build_config(arguments: &ArgMatches) -> Result { - // Use the specified datadir, or default in the home directory - let data_dir: PathBuf = match arguments.value_of("datadir") { - Some(path) => PathBuf::from(path.to_string()), - None => { - let home = dirs::home_dir().ok_or_else(|| { - Error::new(ErrorKind::NotFound, "Unable to determine home directory.") - })?; - home.join(DEFAULT_VALIDATOR_DATADIR) - } +impl Default for Config { + fn default() -> Self { + let data_dir = { + let home = dirs::home_dir().expect("Unable to determine home directory."); + home.join(".lighthouse-validator") }; - fs::create_dir_all(&data_dir)?; + fs::create_dir_all(&data_dir) + .unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir)); - let validator_dir = data_dir.join(DEFAULT_VALIDATORS_SUBDIR); - fs::create_dir_all(&validator_dir)?; + let server = "localhost:50051".to_string(); - let server: String = match arguments.value_of("server") { - Some(srv) => { - //TODO: I don't think this parses correctly a server & port combo - srv.parse::() - .map_err(|e| Error::new(ErrorKind::InvalidInput, e))? - .to_string() - } - None => "localhost:50051".to_string(), + let spec = ChainSpec::foundation(); + + Self { + data_dir, + server, + spec, + } + } +} + +impl Config { + /// Build a new configuration from defaults, which are overrided by arguments provided. + pub fn parse_args(args: &ArgMatches, log: &slog::Logger) -> Result { + let mut config = Config::default(); + + // Use the specified datadir, or default in the home directory + if let Some(datadir) = args.value_of("datadir") { + config.data_dir = PathBuf::from(datadir); + fs::create_dir_all(&config.data_dir) + .unwrap_or_else(|_| panic!("Unable to create {:?}", &config.data_dir)); + info!(log, "Using custom data dir: {:?}", &config.data_dir); + }; + + if let Some(srv) = args.value_of("server") { + //TODO: I don't think this parses correctly a server & port combo + config.server = srv.to_string(); + info!(log, "Using custom server: {:?}", &config.server); }; // TODO: Permit loading a custom spec from file. - let spec: ChainSpec = match arguments.value_of("spec") { - Some(spec_str) => { - match spec_str { - "foundation" => ChainSpec::foundation(), - "few_validators" => ChainSpec::few_validators(), - // Should be impossible due to clap's `possible_values(..)` function. - _ => unreachable!(), - } - } - None => ChainSpec::foundation(), + if let Some(spec_str) = args.value_of("spec") { + info!(log, "Using custom spec: {:?}", spec_str); + config.spec = match spec_str { + "foundation" => ChainSpec::foundation(), + "few_validators" => ChainSpec::few_validators(), + // Should be impossible due to clap's `possible_values(..)` function. + _ => unreachable!(), + }; }; - Ok(Self { - data_dir, - validator_dir, - server, - spec, - }) + Ok(config) } /// Try to load keys from validator_dir, returning None if none are found or an error. - pub fn fetch_keys(&self) -> Result>, Error> { - let mut validator_dirs = fs::read_dir(&self.validator_dir)?.peekable(); + pub fn fetch_keys(&self, log: &slog::Logger) -> Option> { - // There are no validator directories. - if validator_dirs.peek().is_none() { - return Ok(None); + let key_pairs: Vec = fs::read_dir(&self.data_dir) + .unwrap() + .filter_map( |validator_dir| { + + let validator_dir = validator_dir.ok()?; + + if !(validator_dir.file_type().ok()?.is_dir()) { + // Skip non-directories (i.e. no files/symlinks) + return None; + } + + let key_filename = validator_dir.path().join(DEFAULT_PRIVATE_KEY_FILENAME); + + if !(key_filename.is_file()) { + info!(log, "Private key is not a file: {:?}", key_filename.to_str()); + return None; + } + + debug!(log, "Deserializing private key from file: {:?}", key_filename.to_str()); + + let mut key_file = File::open(key_filename.clone()).ok()?; + + let key: Keypair = if let Ok(key_ok) = bincode::deserialize_from(&mut key_file) { + key_ok + } else { + error!(log, "Unable to deserialize the private key file: {:?}", key_filename); + return None; + }; + + let ki = key.identifier(); + if ki != validator_dir.file_name().into_string().ok()? { + error!(log, "The validator key ({:?}) did not match the directory filename {:?}.", ki, &validator_dir.path().to_string_lossy()); + return None; + } + Some(key) + }) + .collect(); + + // Check if it's an empty vector, and return none. + if key_pairs.is_empty() { + None + } else { + Some(key_pairs) } - let mut key_pairs: Vec = Vec::new(); - - for validator_dir_result in validator_dirs { - let validator_dir = validator_dir_result?; - - // Try to open the key file directly - // TODO skip keyfiles that are not found, and log the error instead of returning it. - let mut key_file = File::open(validator_dir.path().join(DEFAULT_PRIVATE_KEY_FILENAME))?; - - let key: Keypair = bincode::deserialize_from(&mut key_file) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; - - // TODO skip keyfile if it's not matched, and log the error instead of returning it. - let validator_directory_name = - validator_dir.file_name().into_string().map_err(|_| { - Error::new( - ErrorKind::InvalidData, - "The filename cannot be parsed to a string.", - ) - })?; - if key.identifier() != validator_directory_name { - return Err(Error::new( - ErrorKind::InvalidData, - "The validator directory ID did not match the key found inside.", - )); - } - - key_pairs.push(key); - } - - Ok(Some(key_pairs)) } /// Saves a keypair to a file inside the appropriate validator directory. Returns the saved path filename. pub fn save_key(&self, key: &Keypair) -> Result { - let validator_config_path = self.validator_dir.join(key.identifier()); + let validator_config_path = self.data_dir.join(key.identifier()); let key_path = validator_config_path.join(DEFAULT_PRIVATE_KEY_FILENAME); fs::create_dir_all(&validator_config_path)?; diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 60361c051..470a070e8 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -1,3 +1,3 @@ pub mod config; -pub use crate::config::ValidatorClientConfig; +pub use crate::config::Config; diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 1830bd1a4..bd0e3e0c5 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,14 +1,14 @@ use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; -use crate::config::ValidatorClientConfig; +use crate::config::Config; use block_proposer::{test_utils::LocalSigner, BlockProducer}; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; use protos::services_grpc::{BeaconBlockServiceClient, ValidatorServiceClient}; -use slog::{error, info, o, Drain}; +use slog::{info, o, Drain}; use slot_clock::SystemTimeSlotClock; use std::sync::Arc; -use std::{process, thread, time}; +use std::thread; mod block_producer_service; mod config; @@ -52,7 +52,7 @@ fn main() { ) .get_matches(); - let config = ValidatorClientConfig::build_config(&matches) + let config = Config::parse_args(&matches, &log) .expect("Unable to build a configuration for the validator client."); // Log configuration @@ -91,15 +91,8 @@ fn main() { let poll_interval_millis = spec.seconds_per_slot * 1000 / 10; // 10% epoch time precision. info!(log, "Starting block producer service"; "polls_per_epoch" => spec.seconds_per_slot * 1000 / poll_interval_millis); - let keypairs = config - .fetch_keys() - .expect("Encountered an error while fetching saved keys.") - .unwrap_or_else(|| { - error!(log, "No key pairs found in configuration, they must first be generated with: account_manager generate."); - // give the logger a chance to flush the error before exiting. - thread::sleep(time::Duration::from_millis(500)); - process::exit(1) - }); + let keypairs = config.fetch_keys(&log) + .expect("No key pairs found in configuration, they must first be generated with: account_manager generate."); /* * Start threads. From cc208670b2bf36cec1faa334c1eed4f89a4365a8 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Sat, 23 Mar 2019 15:52:17 +1100 Subject: [PATCH 25/34] Fixed formatting with rustfmt. --- validator_client/src/config.rs | 39 ++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 3ca066c89..e0bdaea18 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,11 +1,11 @@ use bincode; use bls::Keypair; use clap::ArgMatches; +use slog::{debug, error, info}; use std::fs; use std::fs::File; use std::io::{Error, ErrorKind}; use std::path::PathBuf; -use slog::{debug, info, error}; use types::ChainSpec; /// Stores the core configuration for this validator instance. @@ -77,11 +77,9 @@ impl Config { /// Try to load keys from validator_dir, returning None if none are found or an error. pub fn fetch_keys(&self, log: &slog::Logger) -> Option> { - let key_pairs: Vec = fs::read_dir(&self.data_dir) .unwrap() - .filter_map( |validator_dir| { - + .filter_map(|validator_dir| { let validator_dir = validator_dir.ok()?; if !(validator_dir.file_type().ok()?.is_dir()) { @@ -92,24 +90,40 @@ impl Config { let key_filename = validator_dir.path().join(DEFAULT_PRIVATE_KEY_FILENAME); if !(key_filename.is_file()) { - info!(log, "Private key is not a file: {:?}", key_filename.to_str()); + info!( + log, + "Private key is not a file: {:?}", + key_filename.to_str() + ); return None; } - debug!(log, "Deserializing private key from file: {:?}", key_filename.to_str()); + debug!( + log, + "Deserializing private key from file: {:?}", + key_filename.to_str() + ); let mut key_file = File::open(key_filename.clone()).ok()?; let key: Keypair = if let Ok(key_ok) = bincode::deserialize_from(&mut key_file) { - key_ok - } else { - error!(log, "Unable to deserialize the private key file: {:?}", key_filename); - return None; - }; + key_ok + } else { + error!( + log, + "Unable to deserialize the private key file: {:?}", key_filename + ); + return None; + }; let ki = key.identifier(); if ki != validator_dir.file_name().into_string().ok()? { - error!(log, "The validator key ({:?}) did not match the directory filename {:?}.", ki, &validator_dir.path().to_string_lossy()); + error!( + log, + "The validator key ({:?}) did not match the directory filename {:?}.", + ki, + &validator_dir.path().to_string_lossy() + ); return None; } Some(key) @@ -122,7 +136,6 @@ impl Config { } else { Some(key_pairs) } - } /// Saves a keypair to a file inside the appropriate validator directory. Returns the saved path filename. From 18493a4df4e5ab6569214c8af376269ce3db0d6b Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 24 Mar 2019 09:24:50 +1100 Subject: [PATCH 26/34] Adds microsecond duration to validator client --- validator_client/src/service.rs | 40 +++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 322c370b5..49acd8ad2 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -39,8 +39,8 @@ pub struct Service { slot_clock: Arc, /// The current slot we are processing. current_slot: Slot, - /// Seconds until the next slot. This is used for initializing the tokio timer interval. - seconds_to_next_slot: Duration, + /// Micro seconds until the next slot. This is used for initializing the tokio timer interval. + micros_to_next_slot: Duration, // GRPC Clients /// The beacon block GRPC client. beacon_block_client: Arc, @@ -82,6 +82,7 @@ impl Service { // build requisite objects to form Self let genesis_time = node_info.get_genesis_time(); + let genesis_time = 1_549_935_547; info!(log,"Beacon node connected"; "Node Version" => node_info.version.clone(), "Chain ID" => node_info.chain_id, "Genesis time" => genesis_time); @@ -127,28 +128,45 @@ impl Service { let current_slot = slot_clock.present_slot().unwrap().unwrap().sub(1); // calculate seconds to the next slot - let seconds_to_next_slot = { + let micros_to_next_slot = { let syslot_time = SystemTime::now(); let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); - let mut secs_to_slot = None; + debug!(log, "Duration since unix epoch {:?}", duration_since_epoch); + let mut micros_to_slot = None; if let Some(duration_since_genesis) = duration_since_epoch.checked_sub(Duration::from_secs(genesis_time)) { // seconds till next slot - secs_to_slot = duration_since_genesis + debug!(log, "Genesis Time {:?}", genesis_time); + debug!(log, "Duration since genesis {:?}", duration_since_genesis); + micros_to_slot = duration_since_genesis .as_secs() .checked_rem(config.spec.seconds_per_slot); } - secs_to_slot.unwrap_or_else(|| 0) + micros_to_slot.unwrap_or_else(|| 0) + /* + let duration_to_slot = duration_since_genesis + .checked_sub(Duration::from( + duration_since_genesis + .checked_div(config.spec.seconds_per_slot as u64) + .unwrap() + .as_secs() + .checked_mul(config.spec.seconds_per_slot) + .unwrap(), + )) + .unwrap(); + */ }; + info!(log, ""; "Micro Seconds to next slot"=>micros_to_next_slot); + Self { connected_node_version: node_info.version, chain_id: node_info.chain_id as u16, fork, slot_clock, current_slot, - seconds_to_next_slot: Duration::from_secs(seconds_to_next_slot), + micros_to_next_slot: Duration::from_micros(micros_to_next_slot), beacon_block_client, validator_client, attester_client, @@ -176,7 +194,7 @@ impl Service { // Set the interval to start at the next slot, and every slot after let slot_duration = Duration::from_secs(config.spec.seconds_per_slot); //TODO: Handle checked add correctly - Interval::new(Instant::now() + service.seconds_to_next_slot, slot_duration) + Interval::new(Instant::now() + service.micros_to_next_slot, slot_duration) }; // kick off core service @@ -200,7 +218,11 @@ impl Service { runtime.block_on(interval.for_each(move |_| { // update duties - debug!(service.log, "Processing new slot..."); + debug!( + service.log, + "Processing slot: {}", + service.slot_clock.present_slot().unwrap().unwrap().as_u64() + ); manager.poll(); Ok(()) })); From 4fdb01e5f06df069aa2d0a1e10237d3135004bc8 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 25 Mar 2019 15:10:26 +1100 Subject: [PATCH 27/34] Correct slot duration interval timer --- validator_client/src/service.rs | 67 ++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 49acd8ad2..c88db29b8 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -39,8 +39,8 @@ pub struct Service { slot_clock: Arc, /// The current slot we are processing. current_slot: Slot, - /// Micro seconds until the next slot. This is used for initializing the tokio timer interval. - micros_to_next_slot: Duration, + /// Duration until the next slot. This is used for initializing the tokio timer interval. + duration_to_next_slot: Duration, // GRPC Clients /// The beacon block GRPC client. beacon_block_client: Arc, @@ -76,13 +76,25 @@ impl Service { std::thread::sleep(Duration::from_secs(5)); continue; } - Ok(info) => break info, + Ok(info) => { + if SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + > Duration::from_secs(info.genesis_time) + { + warn!( + log, + "Beacon Node's genesis time is in the future. No work to do.\n Exiting" + ); + // return Err("Genesis Time in the future"); + } + break info; + } }; }; // build requisite objects to form Self let genesis_time = node_info.get_genesis_time(); - let genesis_time = 1_549_935_547; info!(log,"Beacon node connected"; "Node Version" => node_info.version.clone(), "Chain ID" => node_info.chain_id, "Genesis time" => genesis_time); @@ -127,46 +139,38 @@ impl Service { //TODO: Add error chain. Handle errors let current_slot = slot_clock.present_slot().unwrap().unwrap().sub(1); - // calculate seconds to the next slot - let micros_to_next_slot = { + // calculate the duration to the next slot + let duration_to_next_slot = { let syslot_time = SystemTime::now(); let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); - debug!(log, "Duration since unix epoch {:?}", duration_since_epoch); - let mut micros_to_slot = None; + let mut duration_to_next_slot = None; if let Some(duration_since_genesis) = duration_since_epoch.checked_sub(Duration::from_secs(genesis_time)) { - // seconds till next slot - debug!(log, "Genesis Time {:?}", genesis_time); - debug!(log, "Duration since genesis {:?}", duration_since_genesis); - micros_to_slot = duration_since_genesis + let elapsed_slots = duration_since_epoch .as_secs() - .checked_rem(config.spec.seconds_per_slot); + .checked_div(config.spec.seconds_per_slot as u64) + .unwrap(); + duration_to_next_slot = Some( + Duration::from_secs( + (elapsed_slots + 1) + .checked_mul(config.spec.seconds_per_slot) + .unwrap(), + ) + .checked_sub(duration_since_genesis) + .expect("This should never saturate"), + ); } - micros_to_slot.unwrap_or_else(|| 0) - /* - let duration_to_slot = duration_since_genesis - .checked_sub(Duration::from( - duration_since_genesis - .checked_div(config.spec.seconds_per_slot as u64) - .unwrap() - .as_secs() - .checked_mul(config.spec.seconds_per_slot) - .unwrap(), - )) - .unwrap(); - */ + duration_to_next_slot.unwrap_or_else(|| Duration::from_secs(0)) }; - info!(log, ""; "Micro Seconds to next slot"=>micros_to_next_slot); - Self { connected_node_version: node_info.version, chain_id: node_info.chain_id as u16, fork, slot_clock, current_slot, - micros_to_next_slot: Duration::from_micros(micros_to_next_slot), + duration_to_next_slot, beacon_block_client, validator_client, attester_client, @@ -194,7 +198,10 @@ impl Service { // Set the interval to start at the next slot, and every slot after let slot_duration = Duration::from_secs(config.spec.seconds_per_slot); //TODO: Handle checked add correctly - Interval::new(Instant::now() + service.micros_to_next_slot, slot_duration) + Interval::new( + Instant::now() + service.duration_to_next_slot, + slot_duration, + ) }; // kick off core service From ca9af49d4eac240abcf3c4b87237a815f20c03df Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 25 Mar 2019 16:50:15 +1100 Subject: [PATCH 28/34] Adds error handling to validator client service --- beacon_node/client/src/lib.rs | 2 - validator_client/Cargo.toml | 1 + validator_client/src/error.rs | 22 ++++++++ validator_client/src/main.rs | 8 ++- validator_client/src/service.rs | 96 ++++++++++++++++++--------------- 5 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 validator_client/src/error.rs diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index a033da87b..44eab4fe2 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -64,8 +64,6 @@ impl Client { )); } - println!("Here"); - Ok(Client { config, beacon_chain, diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index e8cff2622..ea97ef5d4 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -21,3 +21,4 @@ slog-term = "^2.4.0" slog-async = "^2.3.0" tokio = "0.1.18" tokio-timer = "0.2.10" +error-chain = "0.12.0" diff --git a/validator_client/src/error.rs b/validator_client/src/error.rs new file mode 100644 index 000000000..29d7ba882 --- /dev/null +++ b/validator_client/src/error.rs @@ -0,0 +1,22 @@ +use slot_clock; + +use error_chain::{ + error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed, + impl_extract_backtrace, +}; + +error_chain! { + links { } + + errors { + SlotClockError(e: slot_clock::SystemTimeSlotClockError) { + description("Error reading system time"), + display("SlotClockError: '{:?}'", e) + } + + SystemTimeError(t: String ) { + description("Error reading system time"), + display("SystemTimeError: '{}'", t) + } + } +} diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 0ec392731..127df8494 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -2,12 +2,13 @@ mod attester_service; mod block_producer_service; mod config; mod duties; +pub mod error; mod service; use crate::config::Config as ValidatorConfig; use clap::{App, Arg}; use service::Service as ValidatorService; -use slog::{o, Drain}; +use slog::{error, info, o, Drain}; fn main() { // Logging @@ -50,5 +51,8 @@ fn main() { let config = ValidatorConfig::parse_args(matches, &log).unwrap(); // start the validator service. - ValidatorService::start(config, log); + match ValidatorService::start(config, log.clone()) { + Ok(_) => info!(log, "Validator client shutdown successfully."), + Err(e) => error!(log, "Validator exited due to {:?}", e), + } } diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index c88db29b8..9eeb308db 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -3,6 +3,8 @@ use crate::attester_service::{AttestationGrpcClient, AttesterService}; use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use crate::config::Config as ValidatorConfig; use crate::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; +use crate::error as error_chain; +use crate::error::ErrorKind; use attester::test_utils::EpochMap; use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; use block_proposer::{test_utils::LocalSigner as BlockProposerLocalSigner, BlockProducer}; @@ -13,9 +15,8 @@ use protos::services_grpc::{ AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient, ValidatorServiceClient, }; -use slog::{debug, info, warn}; +use slog::{debug, error, info, warn}; use slot_clock::{SlotClock, SystemTimeSlotClock}; -use std::ops::Sub; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime}; use tokio::prelude::*; @@ -57,7 +58,10 @@ impl Service { /// /// This tries to connect to a beacon node. Once connected, it initialised the gRPC clients /// and returns an instance of the service. - fn initialize_service(config: &ValidatorConfig, log: slog::Logger) -> Self { + fn initialize_service( + config: &ValidatorConfig, + log: slog::Logger, + ) -> error_chain::Result { // initialise the beacon node client to check for a connection let env = Arc::new(EnvBuilder::new().build()); @@ -86,7 +90,7 @@ impl Service { log, "Beacon Node's genesis time is in the future. No work to do.\n Exiting" ); - // return Err("Genesis Time in the future"); + return Err("Genesis time in the future".into()); } break info; } @@ -136,35 +140,37 @@ impl Service { Arc::new(AttestationServiceClient::new(ch)) }; - //TODO: Add error chain. Handle errors - let current_slot = slot_clock.present_slot().unwrap().unwrap().sub(1); + let current_slot = slot_clock + .present_slot() + .map_err(|e| ErrorKind::SlotClockError(e))? + .expect("Genesis must be in the future"); // calculate the duration to the next slot let duration_to_next_slot = { + let seconds_per_slot = config.spec.seconds_per_slot; let syslot_time = SystemTime::now(); - let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); - let mut duration_to_next_slot = None; - if let Some(duration_since_genesis) = - duration_since_epoch.checked_sub(Duration::from_secs(genesis_time)) - { - let elapsed_slots = duration_since_epoch - .as_secs() - .checked_div(config.spec.seconds_per_slot as u64) - .unwrap(); - duration_to_next_slot = Some( - Duration::from_secs( - (elapsed_slots + 1) - .checked_mul(config.spec.seconds_per_slot) - .unwrap(), - ) - .checked_sub(duration_since_genesis) - .expect("This should never saturate"), - ); - } - duration_to_next_slot.unwrap_or_else(|| Duration::from_secs(0)) + let duration_since_epoch = syslot_time + .duration_since(SystemTime::UNIX_EPOCH) + .map_err(|e| ErrorKind::SystemTimeError(e.to_string()))?; + let duration_since_genesis = duration_since_epoch + .checked_sub(Duration::from_secs(genesis_time)) + .expect("Genesis must be in the future. Checked on connection"); + let elapsed_slots = duration_since_epoch + .as_secs() + .checked_div(seconds_per_slot as u64) + .expect("Seconds per slot should not be 0"); + + // the duration to the next slot + Duration::from_secs( + (elapsed_slots + 1) + .checked_mul(seconds_per_slot) + .expect("Next slot time should not overflow u64"), + ) + .checked_sub(duration_since_genesis) + .expect("This should never saturate") }; - Self { + Ok(Self { connected_node_version: node_info.version, chain_id: node_info.chain_id as u16, fork, @@ -175,13 +181,13 @@ impl Service { validator_client, attester_client, log, - } + }) } /// Initialise the service then run the core thread. - pub fn start(config: ValidatorConfig, log: slog::Logger) { + pub fn start(config: ValidatorConfig, log: slog::Logger) -> error_chain::Result<()> { // 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 @@ -190,10 +196,9 @@ impl Service { .clock(Clock::system()) .name_prefix("validator-client-") .build() - .unwrap(); + .map_err(|e| format!("Tokio runtime failed: {}", e))?; // set up the validator work interval - start at next slot and proceed every slot - // TODO: Error chain handle errors. let interval = { // Set the interval to start at the next slot, and every slot after let slot_duration = Duration::from_secs(config.spec.seconds_per_slot); @@ -223,16 +228,23 @@ impl Service { beacon_node: service.validator_client.clone(), }; - runtime.block_on(interval.for_each(move |_| { - // update duties - debug!( - service.log, - "Processing slot: {}", - service.slot_clock.present_slot().unwrap().unwrap().as_u64() - ); - manager.poll(); - Ok(()) - })); + runtime + .block_on(interval.for_each(move |_| { + // update duties + let current_slot = match service.slot_clock.present_slot() { + Err(e) => { + error!(service.log, "SystemTimeError {:?}", e); + return Ok(()); + } + Ok(slot) => slot.expect("Genesis is in the future"), + }; + + debug!(service.log, "Processing slot: {}", current_slot.as_u64()); + manager.poll(); + Ok(()) + })) + .map_err(|e| format!("Service thread failed: {:?}", e))?; + Ok(()) } /* From 3ad18b4367047b4fab37b3946004c06086c8b080 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 25 Mar 2019 17:47:23 +1100 Subject: [PATCH 29/34] Adds manager duties to validator runtime --- validator_client/src/main.rs | 2 +- validator_client/src/service.rs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 1c59513a7..84d0cbff7 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -54,6 +54,6 @@ fn main() { // start the validator service. match ValidatorService::start(config, log.clone()) { Ok(_) => info!(log, "Validator client shutdown successfully."), - Err(e) => error!(log, "Validator exited due to {:?}", e), + Err(e) => error!(log, "Validator exited due to: {}", e.to_string()), } } diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 9eeb308db..720388a61 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -2,6 +2,7 @@ use crate::attester_service::{AttestationGrpcClient, AttesterService}; use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use crate::config::Config as ValidatorConfig; +use crate::duties::PollOutcome; use crate::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; use crate::error as error_chain; use crate::error::ErrorKind; @@ -84,7 +85,8 @@ impl Service { if SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() - > Duration::from_secs(info.genesis_time) + .as_secs() + < info.genesis_time { warn!( log, @@ -239,8 +241,32 @@ impl Service { Ok(slot) => slot.expect("Genesis is in the future"), }; + debug_assert!( + current_slot > service.current_slot, + "The Timer should poll a new slot" + ); + debug!(service.log, "Processing slot: {}", current_slot.as_u64()); - manager.poll(); + + // check for new duties + match manager.poll() { + Err(error) => { + error!(service.log, "Epoch duties poll error"; "error" => format!("{:?}", error)) + } + Ok(PollOutcome::NoChange(epoch)) => { + debug!(service.log, "No change in duties"; "epoch" => epoch) + } + Ok(PollOutcome::DutiesChanged(epoch, duties)) => { + info!(service.log, "Duties changed (potential re-org)"; "epoch" => epoch, "duties" => format!("{:?}", duties)) + } + Ok(PollOutcome::NewDuties(epoch, duties)) => { + info!(service.log, "New duties obtained"; "epoch" => epoch, "duties" => format!("{:?}", duties)) + } + Ok(PollOutcome::UnknownValidatorOrEpoch(epoch)) => { + error!(service.log, "Epoch or validator unknown"; "epoch" => epoch) + } + }; + Ok(()) })) .map_err(|e| format!("Service thread failed: {:?}", e))?; From a8a3f1c31881b65e47a7f6c02e5c6135c2e95514 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 25 Mar 2019 18:03:23 +1100 Subject: [PATCH 30/34] Removes duty manager service in favour of tokio timer --- validator_client/src/duties/mod.rs | 26 ++++++----------- validator_client/src/duties/service.rs | 40 -------------------------- validator_client/src/service.rs | 20 +++++++------ 3 files changed, 19 insertions(+), 67 deletions(-) delete mode 100644 validator_client/src/duties/service.rs diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index c2b95b1c5..f6460afd2 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -1,21 +1,19 @@ mod epoch_duties; mod grpc; -mod service; #[cfg(test)] mod test_node; mod traits; pub use self::epoch_duties::EpochDutiesMap; use self::epoch_duties::{EpochDuties, EpochDutiesMapError}; -pub use self::service::DutiesManagerService; use self::traits::{BeaconNode, BeaconNodeError}; use bls::PublicKey; use slot_clock::SlotClock; use std::sync::Arc; -use types::{ChainSpec, Epoch}; +use types::{ChainSpec, Epoch, Slot}; #[derive(Debug, PartialEq, Clone, Copy)] -pub enum PollOutcome { +pub enum UpdateOutcome { /// The `EpochDuties` were not updated during this poll. NoChange(Epoch), /// The `EpochDuties` for the `epoch` were previously unknown, but obtained in the poll. @@ -50,19 +48,11 @@ pub struct DutiesManager { } impl DutiesManager { - /// Poll the Beacon Node for `EpochDuties`. + /// Check 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.). - //TODO: Remove the poll and trust the tokio system-clock timer. Leave for now to ensure the - //timer is accurate. - pub fn poll(&self) -> Result { - let slot = self - .slot_clock - .present_slot() - .map_err(|_| Error::SlotClockError)? - .ok_or(Error::SlotUnknowable)?; - + pub fn update(&self, slot: Slot) -> Result { let epoch = slot.epoch(self.spec.slots_per_epoch); if let Some(duties) = self @@ -72,17 +62,17 @@ impl DutiesManager { // If these duties were known, check to see if they're updates or identical. let result = if let Some(known_duties) = self.duties_map.get(epoch)? { if known_duties == duties { - Ok(PollOutcome::NoChange(epoch)) + Ok(UpdateOutcome::NoChange(epoch)) } else { - Ok(PollOutcome::DutiesChanged(epoch, duties)) + Ok(UpdateOutcome::DutiesChanged(epoch, duties)) } } else { - Ok(PollOutcome::NewDuties(epoch, duties)) + Ok(UpdateOutcome::NewDuties(epoch, duties)) }; self.duties_map.insert(epoch, duties)?; result } else { - Ok(PollOutcome::UnknownValidatorOrEpoch(epoch)) + Ok(UpdateOutcome::UnknownValidatorOrEpoch(epoch)) } } } diff --git a/validator_client/src/duties/service.rs b/validator_client/src/duties/service.rs deleted file mode 100644 index bdb6faefa..000000000 --- a/validator_client/src/duties/service.rs +++ /dev/null @@ -1,40 +0,0 @@ -use super::traits::BeaconNode; -use super::{DutiesManager, PollOutcome}; -use slog::{debug, error, info, Logger}; -use slot_clock::SlotClock; -use std::time::Duration; - -pub struct DutiesManagerService { - pub manager: DutiesManager, - pub poll_interval_millis: u64, - pub log: Logger, -} - -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() { - Err(error) => { - error!(self.log, "Epoch duties poll error"; "error" => format!("{:?}", error)) - } - Ok(PollOutcome::NoChange(epoch)) => { - debug!(self.log, "No change in duties"; "epoch" => epoch) - } - Ok(PollOutcome::DutiesChanged(epoch, duties)) => { - info!(self.log, "Duties changed (potential re-org)"; "epoch" => epoch, "duties" => format!("{:?}", duties)) - } - Ok(PollOutcome::NewDuties(epoch, duties)) => { - info!(self.log, "New duties obtained"; "epoch" => epoch, "duties" => format!("{:?}", duties)) - } - Ok(PollOutcome::UnknownValidatorOrEpoch(epoch)) => { - error!(self.log, "Epoch or validator unknown"; "epoch" => epoch) - } - }; - - std::thread::sleep(Duration::from_millis(self.poll_interval_millis)); - } - } -} diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 720388a61..8a7e90d10 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -2,8 +2,8 @@ use crate::attester_service::{AttestationGrpcClient, AttesterService}; use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use crate::config::Config as ValidatorConfig; -use crate::duties::PollOutcome; -use crate::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; +use crate::duties::UpdateOutcome; +use crate::duties::{DutiesManager, EpochDutiesMap}; use crate::error as error_chain; use crate::error::ErrorKind; use attester::test_utils::EpochMap; @@ -230,9 +230,10 @@ impl Service { beacon_node: service.validator_client.clone(), }; + // run the core thread runtime .block_on(interval.for_each(move |_| { - // update duties + // get the current slot let current_slot = match service.slot_clock.present_slot() { Err(e) => { error!(service.log, "SystemTimeError {:?}", e); @@ -246,23 +247,24 @@ impl Service { "The Timer should poll a new slot" ); - debug!(service.log, "Processing slot: {}", current_slot.as_u64()); + info!(service.log, "Processing slot: {}", current_slot.as_u64()); // check for new duties - match manager.poll() { + // TODO: Convert to its own thread + match manager.update(current_slot) { Err(error) => { error!(service.log, "Epoch duties poll error"; "error" => format!("{:?}", error)) } - Ok(PollOutcome::NoChange(epoch)) => { + Ok(UpdateOutcome::NoChange(epoch)) => { debug!(service.log, "No change in duties"; "epoch" => epoch) } - Ok(PollOutcome::DutiesChanged(epoch, duties)) => { + Ok(UpdateOutcome::DutiesChanged(epoch, duties)) => { info!(service.log, "Duties changed (potential re-org)"; "epoch" => epoch, "duties" => format!("{:?}", duties)) } - Ok(PollOutcome::NewDuties(epoch, duties)) => { + Ok(UpdateOutcome::NewDuties(epoch, duties)) => { info!(service.log, "New duties obtained"; "epoch" => epoch, "duties" => format!("{:?}", duties)) } - Ok(PollOutcome::UnknownValidatorOrEpoch(epoch)) => { + Ok(UpdateOutcome::UnknownValidatorOrEpoch(epoch)) => { error!(service.log, "Epoch or validator unknown"; "epoch" => epoch) } }; From 4cdeb6abe50f96860921eaab58d938e75793b423 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Mon, 25 Mar 2019 18:32:27 +1100 Subject: [PATCH 31/34] Progress towards validator signing attestations. - Added a 'beacon_attester' RPC endpoint, so the BeaconNode can supply attestation data. - Renamed 'attestation_data' to just 'attestation' throughout (except where it is actually just the data structure). --- beacon_node/beacon_chain/src/beacon_chain.rs | 4 +- .../validator_harness/direct_beacon_node.rs | 6 +- beacon_node/rpc/src/beacon_attester.rs | 61 +++++++++++++++++++ eth2/attester/src/lib.rs | 4 +- .../src/test_utils/simulated_beacon_node.rs | 4 +- eth2/attester/src/traits.rs | 4 +- protos/src/services.proto | 18 +++--- .../attestation_grpc_client.rs | 12 ++-- 8 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 beacon_node/rpc/src/beacon_attester.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 816a570c0..61b5fb58b 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -280,8 +280,8 @@ where } /// Produce an `AttestationData` that is valid for the present `slot` and given `shard`. - pub fn produce_attestation_data(&self, shard: u64) -> Result { - trace!("BeaconChain::produce_attestation_data: shard: {}", shard); + pub fn produce_attestation(&self, shard: u64) -> Result { + trace!("BeaconChain::produce_attestation: shard: {}", shard); let source_epoch = self.state.read().current_justified_epoch; let source_root = *self.state.read().get_block_root( source_epoch.start_slot(self.spec.slots_per_epoch), diff --git a/beacon_node/beacon_chain/test_harness/src/validator_harness/direct_beacon_node.rs b/beacon_node/beacon_chain/test_harness/src/validator_harness/direct_beacon_node.rs index d2de354d7..fde8211ab 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator_harness/direct_beacon_node.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator_harness/direct_beacon_node.rs @@ -50,18 +50,18 @@ impl DirectBeaconNode { } impl AttesterBeaconNode for DirectBeaconNode { - fn produce_attestation_data( + fn produce_attestation( &self, _slot: Slot, shard: u64, ) -> Result, NodeError> { - match self.beacon_chain.produce_attestation_data(shard) { + match self.beacon_chain.produce_attestation(shard) { Ok(attestation_data) => Ok(Some(attestation_data)), Err(e) => Err(NodeError::RemoteFailure(format!("{:?}", e))), } } - fn publish_attestation_data( + fn publish_attestation( &self, free_attestation: FreeAttestation, ) -> Result { diff --git a/beacon_node/rpc/src/beacon_attester.rs b/beacon_node/rpc/src/beacon_attester.rs new file mode 100644 index 000000000..36b6a40b2 --- /dev/null +++ b/beacon_node/rpc/src/beacon_attester.rs @@ -0,0 +1,61 @@ +use futures::Future; +use grpcio::{RpcContext, UnarySink}; +use protos::services::{ + Attestation as AttestationProto, ProduceAttestation, ProduceAttestationResponse, + ProduceAttestationRequest, PublishAttestationResponse, PublishAttestationRequest, + PublishAttestation +}; +use protos::services_grpc::BeaconBlockService; +use slog::Logger; + +#[derive(Clone)] +pub struct AttestationServiceInstance { + pub log: Logger, +} + +impl AttestationService for AttestationServiceInstance { + /// Produce a `BeaconBlock` for signing by a validator. + fn produce_attestation( + &mut self, + ctx: RpcContext, + req: ProduceAttestationRequest, + sink: UnarySink, + ) { + println!("producing attestation at slot {}", req.get_slot()); + + // TODO: build a legit block. + let mut attestation = AttestationProto::new(); + attestation.set_slot(req.get_slot()); + // TODO Set the shard to something legit. + attestation.set_shard(0); + attestation.set_block_root(b"cats".to_vec()); + + let mut resp = ProduceAttestationResponse::new(); + resp.set_attestation_data(attestation); + + let f = sink + .success(resp) + .map_err(move |e| println!("failed to reply {:?}: {:?}", req, e)); + ctx.spawn(f) + } + + /// Accept some fully-formed `BeaconBlock`, process and publish it. + fn publish_attestation( + &mut self, + ctx: RpcContext, + req: PublishAttestationRequest, + sink: UnarySink, + ) { + println!("publishing attestation {:?}", req.get_block()); + + // TODO: actually process the block. + let mut resp = PublishAttestationResponse::new(); + + resp.set_success(true); + + let f = sink + .success(resp) + .map_err(move |e| println!("failed to reply {:?}: {:?}", req, e)); + ctx.spawn(f) + } +} diff --git a/eth2/attester/src/lib.rs b/eth2/attester/src/lib.rs index 8838f022d..065fdc923 100644 --- a/eth2/attester/src/lib.rs +++ b/eth2/attester/src/lib.rs @@ -94,7 +94,7 @@ impl Attester Result { - let attestation_data = match self.beacon_node.produce_attestation_data(slot, shard)? { + let attestation_data = match self.beacon_node.produce_attestation(slot, shard)? { Some(attestation_data) => attestation_data, None => return Ok(PollOutcome::BeaconNodeUnableToProduceAttestation(slot)), }; @@ -120,7 +120,7 @@ impl Attester ProduceResult { + fn produce_attestation(&self, slot: Slot, shard: u64) -> ProduceResult { *self.produce_input.write().unwrap() = Some((slot, shard)); match *self.produce_result.read().unwrap() { Some(ref r) => r.clone(), @@ -34,7 +34,7 @@ impl BeaconNode for SimulatedBeaconNode { } } - fn publish_attestation_data(&self, free_attestation: FreeAttestation) -> PublishResult { + fn publish_attestation(&self, free_attestation: FreeAttestation) -> PublishResult { *self.publish_input.write().unwrap() = Some(free_attestation.clone()); match *self.publish_result.read().unwrap() { Some(ref r) => r.clone(), diff --git a/eth2/attester/src/traits.rs b/eth2/attester/src/traits.rs index 6062460cb..749c6e1a2 100644 --- a/eth2/attester/src/traits.rs +++ b/eth2/attester/src/traits.rs @@ -14,13 +14,13 @@ pub enum PublishOutcome { /// Defines the methods required to produce and publish blocks on a Beacon Node. pub trait BeaconNode: Send + Sync { - fn produce_attestation_data( + fn produce_attestation( &self, slot: Slot, shard: u64, ) -> Result, BeaconNodeError>; - fn publish_attestation_data( + fn publish_attestation( &self, free_attestation: FreeAttestation, ) -> Result; diff --git a/protos/src/services.proto b/protos/src/services.proto index fbcde922d..80d512c54 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -33,8 +33,8 @@ service ValidatorService { /// Service that handles validator attestations service AttestationService { - rpc ProduceAttestationData (ProduceAttestationDataRequest) returns (ProduceAttestationDataResponse); - rpc PublishAttestationData (PublishAttestationDataRequest) returns (PublishAttestationDataResponse); + rpc ProduceAttestation(ProduceAttestationRequest) returns (ProduceAttestationResponse); + rpc PublishAttestation(PublishAttestationRequest) returns (PublishAttestationResponse); } /* @@ -138,20 +138,20 @@ message ProposeBlockSlotResponse { * Attestation Service Messages */ -message ProduceAttestationDataRequest { +message ProduceAttestationRequest { uint64 slot = 1; uint64 shard = 2; } -message ProduceAttestationDataResponse { - AttestationData attestation_data = 1; +message ProduceAttestationResponse { + Attestation attestation_data = 1; } -message PublishAttestationDataRequest { +message PublishAttestationRequest { FreeAttestation free_attestation = 1; } -message PublishAttestationDataResponse { +message PublishAttestationResponse { bool success = 1; bytes msg = 2; } @@ -162,7 +162,7 @@ message Crosslink { } -message AttestationData { +message Attestation { uint64 slot = 1; uint64 shard = 2; bytes beacon_block_root = 3; @@ -175,7 +175,7 @@ message AttestationData { } message FreeAttestation { - AttestationData attestation_data = 1; + Attestation attestation_data = 1; bytes signature = 2; uint64 validator_index = 3; } diff --git a/validator_client/src/attester_service/attestation_grpc_client.rs b/validator_client/src/attester_service/attestation_grpc_client.rs index 566d74a39..5a4701ba9 100644 --- a/validator_client/src/attester_service/attestation_grpc_client.rs +++ b/validator_client/src/attester_service/attestation_grpc_client.rs @@ -2,7 +2,7 @@ use protos::services_grpc::AttestationServiceClient; use std::sync::Arc; use attester::{BeaconNode, BeaconNodeError, PublishOutcome}; -use protos::services::ProduceAttestationDataRequest; +use protos::services::ProduceAttestationRequest; use types::{AttestationData, FreeAttestation, Slot}; pub struct AttestationGrpcClient { @@ -16,25 +16,25 @@ impl AttestationGrpcClient { } impl BeaconNode for AttestationGrpcClient { - fn produce_attestation_data( + fn produce_attestation( &self, slot: Slot, shard: u64, ) -> Result, BeaconNodeError> { - let mut req = ProduceAttestationDataRequest::new(); + let mut req = ProduceAttestationRequest::new(); req.set_slot(slot.as_u64()); req.set_shard(shard); let reply = self .client - .produce_attestation_data(&req) + .produce_attestation(&req) .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; - // TODO: return correct AttestationData + // TODO: return correct Attestation Err(BeaconNodeError::DecodeFailure) } - fn publish_attestation_data( + fn publish_attestation( &self, free_attestation: FreeAttestation, ) -> Result { From 05369df7e8a8d82a875224b4f4dea242723081ee Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 25 Mar 2019 22:00:11 +1100 Subject: [PATCH 32/34] Add PubsubMessage and publish function to behaviour --- beacon_node/client/src/lib.rs | 3 +- beacon_node/eth2-libp2p/src/behaviour.rs | 56 +++++++++++++++++++++--- beacon_node/network/src/beacon_chain.rs | 2 +- beacon_node/network/src/service.rs | 13 +++++- beacon_node/rpc/src/beacon_block.rs | 11 ++++- beacon_node/rpc/src/lib.rs | 5 ++- 6 files changed, 78 insertions(+), 12 deletions(-) diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 44eab4fe2..b24d2cb7f 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -46,7 +46,7 @@ impl Client { // TODO: Add beacon_chain reference to network parameters let network_config = &config.net_conf; let network_logger = log.new(o!("Service" => "Network")); - let (network, _network_send) = NetworkService::new( + let (network, network_send) = NetworkService::new( beacon_chain.clone(), network_config, executor, @@ -59,6 +59,7 @@ impl Client { rpc_exit_signal = Some(rpc::start_server( &config.rpc_conf, executor, + network_send, beacon_chain.clone(), &log, )); diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 3d5b94353..b3c4213b1 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -14,6 +14,7 @@ use libp2p::{ NetworkBehaviour, PeerId, }; use slog::{debug, o}; +use ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream}; use ssz_derive::{Decode, Encode}; use types::Attestation; use types::Topic; @@ -124,6 +125,15 @@ impl Behaviour { } } + /* Behaviour functions */ + + /// Publishes a message on the pubsub (gossipsub) behaviour. + pub fn publish(&mut self, topic: Topic, message: PubsubMessage) { + //encode the message + let message_bytes = ssz_encode(&message); + self.gossipsub.publish(topic, message_bytes); + } + /// Consumes the events list when polled. fn poll( &mut self, @@ -158,12 +168,6 @@ pub enum BehaviourEvent { Message(String), } -#[derive(Debug, Clone)] -pub enum IncomingGossip { - Block(BlockGossip), - Attestation(AttestationGossip), -} - /// Gossipsub message providing notification of a new block. #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BlockGossip { @@ -175,3 +179,43 @@ pub struct BlockGossip { pub struct AttestationGossip { pub attestation: Attestation, } + +/// Messages that are passed to and from the pubsub (Gossipsub) behaviour. +#[derive(Debug, Clone)] +pub enum PubsubMessage { + Block(BlockGossip), + Attestation(AttestationGossip), +} + +//TODO: Correctly encode/decode enums. Prefixing with integer for now. +impl Encodable for PubsubMessage { + fn ssz_append(&self, s: &mut SszStream) { + match self { + PubsubMessage::Block(block_gossip) => { + 0u32.ssz_append(s); + block_gossip.ssz_append(s); + } + PubsubMessage::Attestation(attestation_gossip) => { + 1u32.ssz_append(s); + attestation_gossip.ssz_append(s); + } + } + } +} + +impl Decodable for PubsubMessage { + fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError> { + let (id, index) = u32::ssz_decode(bytes, index)?; + match id { + 1 => { + let (block, index) = BlockGossip::ssz_decode(bytes, index)?; + Ok((PubsubMessage::Block(block), index)) + } + 2 => { + let (attestation, index) = AttestationGossip::ssz_decode(bytes, index)?; + Ok((PubsubMessage::Attestation(attestation), index)) + } + _ => Err(DecodeError::Invalid), + } + } +} diff --git a/beacon_node/network/src/beacon_chain.rs b/beacon_node/network/src/beacon_chain.rs index 26cea0065..c627912a4 100644 --- a/beacon_node/network/src/beacon_chain.rs +++ b/beacon_node/network/src/beacon_chain.rs @@ -109,7 +109,7 @@ where let state = self.get_state(); HelloMessage { - network_id: spec.network_id, + network_id: spec.chain_id, latest_finalized_root: state.finalized_root, latest_finalized_epoch: state.finalized_epoch, best_root: self.best_block_root(), diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index a3eb6f0d9..33ea79c1a 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -3,7 +3,7 @@ use crate::error; use crate::message_handler::{HandlerMessage, MessageHandler}; use crate::NetworkConfig; use crossbeam_channel::{unbounded as channel, Sender, TryRecvError}; -use eth2_libp2p::RPCEvent; +use eth2_libp2p::{RPCEvent, PublishMessage}; use eth2_libp2p::Service as LibP2PService; use eth2_libp2p::{Libp2pEvent, PeerId}; use futures::prelude::*; @@ -12,6 +12,7 @@ use futures::Stream; use slog::{debug, info, o, trace}; use std::sync::Arc; use tokio::runtime::TaskExecutor; +use types::{BeaconBlock, Topic}; /// Service that handles communication between internal services and the eth2_libp2p network service. pub struct Service { @@ -161,7 +162,12 @@ fn network_service( return Err(eth2_libp2p::error::Error::from( "Network channel disconnected", )); - } + }, + Ok(NetworkMessage::Publish(topic, message) => { + debug!(log, "Sending message on topic {:?}", topic); + libp2p_service.swarm.publish(topic,message) + + } } Ok(Async::NotReady) @@ -174,6 +180,8 @@ pub enum NetworkMessage { /// Send a message to libp2p service. //TODO: Define typing for messages across the wire Send(PeerId, OutgoingMessage), + /// Publish a message to gossipsub + Publish(Topic, PublishMessage), } /// Type of outgoing messages that can be sent through the network service. @@ -184,3 +192,4 @@ pub enum OutgoingMessage { //TODO: Remove NotifierTest, } + diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index 96f64e0dd..9169d695d 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -9,6 +9,7 @@ use slog::Logger; #[derive(Clone)] pub struct BeaconBlockServiceInstance { + network_chan: crossbeam_channel::Sender, pub log: Logger, } @@ -43,7 +44,15 @@ impl BeaconBlockService for BeaconBlockServiceInstance { req: PublishBeaconBlockRequest, sink: UnarySink, ) { - println!("publishing {:?}", req.get_block()); + let block = req.get_block(); + println!("publishing {:?}", block); + + + // TODO: Build properly + let topic = types::TopicBuilder:: + println!("Sending beacon block to gossipsub"); + network_chan.send(NetworkMessage::Publish( + // TODO: actually process the block. let mut resp = PublishBeaconBlockResponse::new(); diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index 3c89bda1f..e1267270c 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -21,6 +21,7 @@ use tokio::runtime::TaskExecutor; pub fn start_server( config: &RPCConfig, executor: &TaskExecutor, + network_chan: crossbeam_channel::Sender, beacon_chain: Arc, log: &slog::Logger, ) -> exit_future::Signal { @@ -40,7 +41,9 @@ pub fn start_server( }; let beacon_block_service = { - let instance = BeaconBlockServiceInstance { log: log.clone() }; + let instance = BeaconBlockServiceInstance { + network_chan + log: log.clone() }; create_beacon_block_service(instance) }; let validator_service = { From 52b31b200940850b265ae585d567f3a286f0fae1 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 25 Mar 2019 23:02:51 +1100 Subject: [PATCH 33/34] Implement initial pubsub message handling --- beacon_node/eth2-libp2p/src/behaviour.rs | 57 +++++++++++++++------- beacon_node/eth2-libp2p/src/lib.rs | 3 +- beacon_node/eth2-libp2p/src/service.rs | 26 +++++++--- beacon_node/network/src/beacon_chain.rs | 2 +- beacon_node/network/src/message_handler.rs | 12 ++--- beacon_node/network/src/service.rs | 36 ++++++++------ beacon_node/rpc/Cargo.toml | 2 + beacon_node/rpc/src/beacon_block.rs | 6 ++- eth2/types/src/lib.rs | 2 +- 9 files changed, 95 insertions(+), 51 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index b3c4213b1..0229d06d5 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -13,11 +13,11 @@ use libp2p::{ tokio_io::{AsyncRead, AsyncWrite}, NetworkBehaviour, PeerId, }; -use slog::{debug, o}; +use slog::{debug, o, warn}; use ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream}; use ssz_derive::{Decode, Encode}; use types::Attestation; -use types::Topic; +use types::{Topic, TopicHash}; /// Builds the network behaviour for the libp2p Swarm. /// Implements gossipsub message routing. @@ -48,13 +48,33 @@ impl NetworkBehaviourEventProcess { - let gs_message = String::from_utf8_lossy(&message.data); - // TODO: Remove this type - debug only - self.events - .push(BehaviourEvent::Message(gs_message.to_string())) + GossipsubEvent::Message(gs_msg) => { + let pubsub_message = match PubsubMessage::ssz_decode(&gs_msg.data, 0) { + //TODO: Punish peer on error + Err(e) => { + warn!( + self.log, + "Received undecodable message from Peer {:?}", gs_msg.source + ); + return; + } + Ok((msg, _index)) => msg, + }; + + self.events.push(BehaviourEvent::GossipMessage { + source: gs_msg.source, + topics: gs_msg.topics, + message: pubsub_message, + }); } - _ => {} + GossipsubEvent::Subscribed { + peer_id: _, + topic: _, + } + | GossipsubEvent::Unsubscribed { + peer_id: _, + topic: _, + } => {} } } } @@ -125,15 +145,6 @@ impl Behaviour { } } - /* Behaviour functions */ - - /// Publishes a message on the pubsub (gossipsub) behaviour. - pub fn publish(&mut self, topic: Topic, message: PubsubMessage) { - //encode the message - let message_bytes = ssz_encode(&message); - self.gossipsub.publish(topic, message_bytes); - } - /// Consumes the events list when polled. fn poll( &mut self, @@ -157,6 +168,12 @@ impl Behaviour { pub fn send_rpc(&mut self, peer_id: PeerId, rpc_event: RPCEvent) { self.serenity_rpc.send_rpc(peer_id, rpc_event); } + + /// Publishes a message on the pubsub (gossipsub) behaviour. + pub fn publish(&mut self, topic: Topic, message: PubsubMessage) { + let message_bytes = ssz_encode(&message); + self.gossipsub.publish(topic, message_bytes); + } } /// The types of events than can be obtained from polling the behaviour. @@ -165,7 +182,11 @@ pub enum BehaviourEvent { PeerDialed(PeerId), Identified(PeerId, IdentifyInfo), // TODO: This is a stub at the moment - Message(String), + GossipMessage { + source: PeerId, + topics: Vec, + message: PubsubMessage, + }, } /// Gossipsub message providing notification of a new block. diff --git a/beacon_node/eth2-libp2p/src/lib.rs b/beacon_node/eth2-libp2p/src/lib.rs index f7a961bb2..659d6b01c 100644 --- a/beacon_node/eth2-libp2p/src/lib.rs +++ b/beacon_node/eth2-libp2p/src/lib.rs @@ -8,12 +8,13 @@ pub mod error; pub mod rpc; mod service; +pub use behaviour::PubsubMessage; pub use config::Config as NetworkConfig; pub use libp2p::{ gossipsub::{GossipsubConfig, GossipsubConfigBuilder}, PeerId, }; -pub use rpc::{HelloMessage, RPCEvent}; +pub use rpc::RPCEvent; pub use service::Libp2pEvent; pub use service::Service; pub use types::multiaddr; diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index e68df2d38..73facee72 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -1,4 +1,4 @@ -use crate::behaviour::{Behaviour, BehaviourEvent}; +use crate::behaviour::{Behaviour, BehaviourEvent, PubsubMessage}; use crate::error; use crate::multiaddr::Protocol; use crate::rpc::RPCEvent; @@ -16,7 +16,7 @@ use libp2p::{core, secio, PeerId, Swarm, Transport}; use slog::{debug, info, trace, warn}; use std::io::{Error, ErrorKind}; use std::time::Duration; -use types::TopicBuilder; +use types::{TopicBuilder, TopicHash}; /// The configuration and state of the libp2p components for the beacon node. pub struct Service { @@ -107,9 +107,17 @@ impl Stream for Service { //Behaviour events Ok(Async::Ready(Some(event))) => match event { // TODO: Stub here for debugging - BehaviourEvent::Message(m) => { - debug!(self.log, "Message received: {}", m); - return Ok(Async::Ready(Some(Libp2pEvent::Message(m)))); + BehaviourEvent::GossipMessage { + source, + topics, + message, + } => { + debug!(self.log, "Pubsub message received: {:?}", message); + return Ok(Async::Ready(Some(Libp2pEvent::PubsubMessage { + source, + topics, + message, + }))); } BehaviourEvent::RPC(peer_id, event) => { return Ok(Async::Ready(Some(Libp2pEvent::RPC(peer_id, event)))); @@ -173,6 +181,10 @@ pub enum Libp2pEvent { PeerDialed(PeerId), /// Received information about a peer on the network. Identified(PeerId, IdentifyInfo), - // TODO: Pub-sub testing only. - Message(String), + /// Received pubsub message. + PubsubMessage { + source: PeerId, + topics: Vec, + message: PubsubMessage, + }, } diff --git a/beacon_node/network/src/beacon_chain.rs b/beacon_node/network/src/beacon_chain.rs index c627912a4..8ec8162ff 100644 --- a/beacon_node/network/src/beacon_chain.rs +++ b/beacon_node/network/src/beacon_chain.rs @@ -7,7 +7,7 @@ use beacon_chain::{ types::{BeaconState, ChainSpec}, AggregationOutcome, CheckPoint, }; -use eth2_libp2p::HelloMessage; +use eth2_libp2p::rpc::HelloMessage; use types::{Attestation, BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; pub use beacon_chain::{BeaconChainError, BlockProcessingOutcome}; diff --git a/beacon_node/network/src/message_handler.rs b/beacon_node/network/src/message_handler.rs index dcfee96a0..0efa6b96f 100644 --- a/beacon_node/network/src/message_handler.rs +++ b/beacon_node/network/src/message_handler.rs @@ -4,7 +4,7 @@ use crate::service::{NetworkMessage, OutgoingMessage}; use crate::sync::SimpleSync; use crossbeam_channel::{unbounded as channel, Sender}; use eth2_libp2p::{ - behaviour::IncomingGossip, + behaviour::PubsubMessage, rpc::{methods::GoodbyeReason, RPCRequest, RPCResponse, RequestId}, PeerId, RPCEvent, }; @@ -41,7 +41,7 @@ pub enum HandlerMessage { /// An RPC response/request has been received. RPC(PeerId, RPCEvent), /// A gossip message has been received. - IncomingGossip(PeerId, IncomingGossip), + PubsubMessage(PeerId, PubsubMessage), } impl MessageHandler { @@ -92,7 +92,7 @@ impl MessageHandler { self.handle_rpc_message(peer_id, rpc_event); } // we have received an RPC message request/response - HandlerMessage::IncomingGossip(peer_id, gossip) => { + HandlerMessage::PubsubMessage(peer_id, gossip) => { self.handle_gossip(peer_id, gossip); } //TODO: Handle all messages @@ -205,13 +205,13 @@ impl MessageHandler { } /// Handle RPC messages - fn handle_gossip(&mut self, peer_id: PeerId, gossip_message: IncomingGossip) { + fn handle_gossip(&mut self, peer_id: PeerId, gossip_message: PubsubMessage) { match gossip_message { - IncomingGossip::Block(message) => { + PubsubMessage::Block(message) => { self.sync .on_block_gossip(peer_id, message, &mut self.network_context) } - IncomingGossip::Attestation(message) => { + PubsubMessage::Attestation(message) => { self.sync .on_attestation_gossip(peer_id, message, &mut self.network_context) } diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 33ea79c1a..55c43e4ec 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -3,16 +3,16 @@ use crate::error; use crate::message_handler::{HandlerMessage, MessageHandler}; use crate::NetworkConfig; use crossbeam_channel::{unbounded as channel, Sender, TryRecvError}; -use eth2_libp2p::{RPCEvent, PublishMessage}; use eth2_libp2p::Service as LibP2PService; use eth2_libp2p::{Libp2pEvent, PeerId}; +use eth2_libp2p::{PubsubMessage, RPCEvent}; use futures::prelude::*; use futures::sync::oneshot; use futures::Stream; use slog::{debug, info, o, trace}; use std::sync::Arc; use tokio::runtime::TaskExecutor; -use types::{BeaconBlock, Topic}; +use types::Topic; /// Service that handles communication between internal services and the eth2_libp2p network service. pub struct Service { @@ -100,6 +100,7 @@ fn spawn_service( Ok(network_exit) } +//TODO: Potentially handle channel errors fn network_service( mut libp2p_service: LibP2PService, network_recv: crossbeam_channel::Receiver, @@ -129,10 +130,17 @@ fn network_service( "We have identified peer: {:?} with {:?}", peer_id, info ); } - Libp2pEvent::Message(m) => debug!( - libp2p_service.log, - "Network Service: Message received: {}", m - ), + Libp2pEvent::PubsubMessage { + source, + topics: _, + message, + } => { + //TODO: Decide if we need to propagate the topic upwards. (Potentially for + //attestations) + message_handler_send + .send(HandlerMessage::PubsubMessage(source, message)) + .map_err(|_| " failed to send pubsub message to handler")?; + } }, Ok(Async::Ready(None)) => unreachable!("Stream never ends"), Ok(Async::NotReady) => break, @@ -157,17 +165,16 @@ fn network_service( } }; } + Ok(NetworkMessage::Publish(topic, message)) => { + debug!(log, "Sending pubsub message on topic {:?}", topic); + libp2p_service.swarm.publish(topic, message); + } Err(TryRecvError::Empty) => break, Err(TryRecvError::Disconnected) => { return Err(eth2_libp2p::error::Error::from( "Network channel disconnected", )); - }, - Ok(NetworkMessage::Publish(topic, message) => { - debug!(log, "Sending message on topic {:?}", topic); - libp2p_service.swarm.publish(topic,message) - - + } } } Ok(Async::NotReady) @@ -180,8 +187,8 @@ pub enum NetworkMessage { /// Send a message to libp2p service. //TODO: Define typing for messages across the wire Send(PeerId, OutgoingMessage), - /// Publish a message to gossipsub - Publish(Topic, PublishMessage), + /// Publish a message to pubsub mechanism. + Publish(Topic, PubsubMessage), } /// Type of outgoing messages that can be sent through the network service. @@ -192,4 +199,3 @@ pub enum OutgoingMessage { //TODO: Remove NotifierTest, } - diff --git a/beacon_node/rpc/Cargo.toml b/beacon_node/rpc/Cargo.toml index d405982db..e9709c1ce 100644 --- a/beacon_node/rpc/Cargo.toml +++ b/beacon_node/rpc/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] bls = { path = "../../eth2/utils/bls" } beacon_chain = { path = "../beacon_chain" } +network = { path = "../network" } version = { path = "../version" } types = { path = "../../eth2/types" } ssz = { path = "../../eth2/utils/ssz" } @@ -23,3 +24,4 @@ slog-term = "^2.4.0" slog-async = "^2.3.0" tokio = "0.1.17" exit-future = "0.1.4" +crossbeam-channel = "0.3.8" diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index 9169d695d..d124152f1 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -6,6 +6,8 @@ use protos::services::{ }; use protos::services_grpc::BeaconBlockService; use slog::Logger; +use crossbeam_channel; +use network::NetworkMessage; #[derive(Clone)] pub struct BeaconBlockServiceInstance { @@ -48,8 +50,8 @@ impl BeaconBlockService for BeaconBlockServiceInstance { println!("publishing {:?}", block); - // TODO: Build properly - let topic = types::TopicBuilder:: + // TODO: Obtain from the network properly. + let topic = types::TopicBuilder::from("beacon_chain").build(); println!("Sending beacon block to gossipsub"); network_chan.send(NetworkMessage::Publish( diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 953a9508f..118e862e8 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -85,6 +85,6 @@ pub type AttesterMap = HashMap<(u64, u64), Vec>; pub type ProposerMap = HashMap; pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, SecretKey, Signature}; -pub use libp2p::floodsub::{Topic, TopicBuilder}; +pub use libp2p::floodsub::{Topic, TopicBuilder, TopicHash}; pub use libp2p::multiaddr; pub use libp2p::Multiaddr; From f7131c2f871fa521c2418ba51f56b22014a25507 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 25 Mar 2019 23:39:39 +1100 Subject: [PATCH 34/34] Fix compile issues and modify type names --- beacon_node/eth2-libp2p/src/behaviour.rs | 28 ++++++++------------- beacon_node/network/src/lib.rs | 1 + beacon_node/network/src/service.rs | 11 +++++--- beacon_node/network/src/sync/simple_sync.rs | 19 +++++++------- beacon_node/rpc/Cargo.toml | 1 + beacon_node/rpc/src/beacon_block.rs | 28 ++++++++++++++------- beacon_node/rpc/src/lib.rs | 6 +++-- 7 files changed, 51 insertions(+), 43 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 0229d06d5..41b7c8965 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -170,9 +170,11 @@ impl Behaviour { } /// Publishes a message on the pubsub (gossipsub) behaviour. - pub fn publish(&mut self, topic: Topic, message: PubsubMessage) { + pub fn publish(&mut self, topics: Vec, message: PubsubMessage) { let message_bytes = ssz_encode(&message); - self.gossipsub.publish(topic, message_bytes); + for topic in topics { + self.gossipsub.publish(topic, message_bytes.clone()); + } } } @@ -189,23 +191,13 @@ pub enum BehaviourEvent { }, } -/// Gossipsub message providing notification of a new block. -#[derive(Encode, Decode, Clone, Debug, PartialEq)] -pub struct BlockGossip { - pub root: BlockRootSlot, -} - -/// Gossipsub message providing notification of a new attestation. -#[derive(Encode, Decode, Clone, Debug, PartialEq)] -pub struct AttestationGossip { - pub attestation: Attestation, -} - /// Messages that are passed to and from the pubsub (Gossipsub) behaviour. #[derive(Debug, Clone)] pub enum PubsubMessage { - Block(BlockGossip), - Attestation(AttestationGossip), + /// Gossipsub message providing notification of a new block. + Block(BlockRootSlot), + /// Gossipsub message providing notification of a new attestation. + Attestation(Attestation), } //TODO: Correctly encode/decode enums. Prefixing with integer for now. @@ -229,11 +221,11 @@ impl Decodable for PubsubMessage { let (id, index) = u32::ssz_decode(bytes, index)?; match id { 1 => { - let (block, index) = BlockGossip::ssz_decode(bytes, index)?; + let (block, index) = BlockRootSlot::ssz_decode(bytes, index)?; Ok((PubsubMessage::Block(block), index)) } 2 => { - let (attestation, index) = AttestationGossip::ssz_decode(bytes, index)?; + let (attestation, index) = Attestation::ssz_decode(bytes, index)?; Ok((PubsubMessage::Attestation(attestation), index)) } _ => Err(DecodeError::Invalid), diff --git a/beacon_node/network/src/lib.rs b/beacon_node/network/src/lib.rs index 87f8368a5..c298e31b4 100644 --- a/beacon_node/network/src/lib.rs +++ b/beacon_node/network/src/lib.rs @@ -6,4 +6,5 @@ pub mod service; pub mod sync; pub use eth2_libp2p::NetworkConfig; +pub use service::NetworkMessage; pub use service::Service; diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 55c43e4ec..b2d2b5a24 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -165,9 +165,9 @@ fn network_service( } }; } - Ok(NetworkMessage::Publish(topic, message)) => { - debug!(log, "Sending pubsub message on topic {:?}", topic); - libp2p_service.swarm.publish(topic, message); + Ok(NetworkMessage::Publish { topics, message }) => { + debug!(log, "Sending pubsub message on topics {:?}", topics); + libp2p_service.swarm.publish(topics, message); } Err(TryRecvError::Empty) => break, Err(TryRecvError::Disconnected) => { @@ -188,7 +188,10 @@ pub enum NetworkMessage { //TODO: Define typing for messages across the wire Send(PeerId, OutgoingMessage), /// Publish a message to pubsub mechanism. - Publish(Topic, PubsubMessage), + Publish { + topics: Vec, + message: PubsubMessage, + }, } /// Type of outgoing messages that can be sent through the network service. diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 4c08a6871..2aa0a1d7d 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -1,7 +1,6 @@ use super::import_queue::ImportQueue; use crate::beacon_chain::BeaconChain; use crate::message_handler::NetworkContext; -use eth2_libp2p::behaviour::{AttestationGossip, BlockGossip}; use eth2_libp2p::rpc::methods::*; use eth2_libp2p::rpc::{RPCRequest, RPCResponse, RequestId}; use eth2_libp2p::PeerId; @@ -9,7 +8,7 @@ use slog::{debug, error, info, o, warn}; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; -use types::{Epoch, Hash256, Slot}; +use types::{Attestation, Epoch, Hash256, Slot}; /// The number of slots that we can import blocks ahead of us, before going into full Sync mode. const SLOT_IMPORT_TOLERANCE: u64 = 100; @@ -521,12 +520,12 @@ impl SimpleSync { pub fn on_block_gossip( &mut self, peer_id: PeerId, - msg: BlockGossip, + msg: BlockRootSlot, network: &mut NetworkContext, ) { debug!( self.log, - "BlockGossip"; + "BlockSlot"; "peer" => format!("{:?}", peer_id), ); // TODO: filter out messages that a prior to the finalized slot. @@ -535,12 +534,12 @@ impl SimpleSync { // now. // // Note: only requests the new block -- will fail if we don't have its parents. - if self.import_queue.is_new_block(&msg.root.block_root) { + if self.import_queue.is_new_block(&msg.block_root) { self.request_block_headers( peer_id, BeaconBlockHeadersRequest { - start_root: msg.root.block_root, - start_slot: msg.root.slot, + start_root: msg.block_root, + start_slot: msg.slot, max_headers: 1, skip_slots: 0, }, @@ -555,19 +554,19 @@ impl SimpleSync { pub fn on_attestation_gossip( &mut self, peer_id: PeerId, - msg: AttestationGossip, + msg: Attestation, _network: &mut NetworkContext, ) { debug!( self.log, - "AttestationGossip"; + "Attestation"; "peer" => format!("{:?}", peer_id), ); // Awaiting a proper operations pool before we can import attestations. // // https://github.com/sigp/lighthouse/issues/281 - match self.chain.process_attestation(msg.attestation) { + match self.chain.process_attestation(msg) { Ok(_) => panic!("Impossible, method not implemented."), Err(_) => error!(self.log, "Attestation processing not implemented!"), } diff --git a/beacon_node/rpc/Cargo.toml b/beacon_node/rpc/Cargo.toml index e9709c1ce..3fc52c6b1 100644 --- a/beacon_node/rpc/Cargo.toml +++ b/beacon_node/rpc/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" bls = { path = "../../eth2/utils/bls" } beacon_chain = { path = "../beacon_chain" } network = { path = "../network" } +eth2-libp2p = { path = "../eth2-libp2p" } version = { path = "../version" } types = { path = "../../eth2/types" } ssz = { path = "../../eth2/utils/ssz" } diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index d124152f1..4e1875665 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -1,17 +1,20 @@ +use crossbeam_channel; +use eth2_libp2p::rpc::methods::BlockRootSlot; +use eth2_libp2p::PubsubMessage; use futures::Future; use grpcio::{RpcContext, UnarySink}; +use network::NetworkMessage; use protos::services::{ BeaconBlock as BeaconBlockProto, ProduceBeaconBlockRequest, ProduceBeaconBlockResponse, PublishBeaconBlockRequest, PublishBeaconBlockResponse, }; use protos::services_grpc::BeaconBlockService; use slog::Logger; -use crossbeam_channel; -use network::NetworkMessage; +use types::{Hash256, Slot}; #[derive(Clone)] pub struct BeaconBlockServiceInstance { - network_chan: crossbeam_channel::Sender, + pub network_chan: crossbeam_channel::Sender, pub log: Logger, } @@ -47,14 +50,21 @@ impl BeaconBlockService for BeaconBlockServiceInstance { sink: UnarySink, ) { let block = req.get_block(); - println!("publishing {:?}", block); + let block_root = Hash256::from_slice(block.get_block_root()); + let block_slot = BlockRootSlot { + block_root, + slot: Slot::from(block.get_slot()), + }; + println!("publishing block with root {:?}", block_root); - - // TODO: Obtain from the network properly. - let topic = types::TopicBuilder::from("beacon_chain").build(); + // TODO: Obtain topics from the network service properly. + let topic = types::TopicBuilder::new("beacon_chain".to_string()).build(); + let message = PubsubMessage::Block(block_slot); println!("Sending beacon block to gossipsub"); - network_chan.send(NetworkMessage::Publish( - + self.network_chan.send(NetworkMessage::Publish { + topics: vec![topic], + message, + }); // TODO: actually process the block. let mut resp = PublishBeaconBlockResponse::new(); diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index e1267270c..4dfd33487 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -11,6 +11,7 @@ use self::validator::ValidatorServiceInstance; pub use config::Config as RPCConfig; use futures::{future, Future}; use grpcio::{Environment, Server, ServerBuilder}; +use network::NetworkMessage; use protos::services_grpc::{ create_beacon_block_service, create_beacon_node_service, create_validator_service, }; @@ -42,8 +43,9 @@ pub fn start_server( let beacon_block_service = { let instance = BeaconBlockServiceInstance { - network_chan - log: log.clone() }; + network_chan, + log: log.clone(), + }; create_beacon_block_service(instance) }; let validator_service = {