From 1584469b7c754da9c45f36bf944ea14a21292da7 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Tue, 26 Mar 2019 17:41:43 +1100 Subject: [PATCH 01/57] Renamed attestation -> attestation_data for fetch, but not publish, to acknowledge the difference in the spec. Also started implementing the gRPC get_attestation_data functionality in the BeaconNode. --- .../validator_harness/direct_beacon_node.rs | 2 +- beacon_node/rpc/src/beacon_attester.rs | 59 +++++++++++++------ eth2/attester/src/lib.rs | 2 +- .../src/test_utils/simulated_beacon_node.rs | 2 +- eth2/attester/src/traits.rs | 2 +- protos/src/services.proto | 12 ++-- .../attestation_grpc_client.rs | 6 +- 7 files changed, 53 insertions(+), 32 deletions(-) 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 fde8211ab5..17630833bb 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,7 +50,7 @@ impl DirectBeaconNode { } impl AttesterBeaconNode for DirectBeaconNode { - fn produce_attestation( + fn produce_attestation_data( &self, _slot: Slot, shard: u64, diff --git a/beacon_node/rpc/src/beacon_attester.rs b/beacon_node/rpc/src/beacon_attester.rs index 36b6a40b24..88caacdd0b 100644 --- a/beacon_node/rpc/src/beacon_attester.rs +++ b/beacon_node/rpc/src/beacon_attester.rs @@ -1,45 +1,66 @@ +use crate::beacon_chain::BeaconChain; use futures::Future; -use grpcio::{RpcContext, UnarySink}; +use grpcio::{RpcContext, UnarySink, RpcStatus, RpcStatusCode}; use protos::services::{ - Attestation as AttestationProto, ProduceAttestation, ProduceAttestationResponse, - ProduceAttestationRequest, PublishAttestationResponse, PublishAttestationRequest, + AttestationData as AttestationDataProto, ProduceAttestationData, ProduceAttestationDataResponse, + ProduceAttestationDataRequest, PublishAttestationResponse, PublishAttestationRequest, PublishAttestation }; use protos::services_grpc::BeaconBlockService; -use slog::Logger; +use slog::{Logger, info, warn, error}; #[derive(Clone)] pub struct AttestationServiceInstance { + pub chain: Arc, pub log: Logger, } impl AttestationService for AttestationServiceInstance { - /// Produce a `BeaconBlock` for signing by a validator. - fn produce_attestation( + /// Produce the `AttestationData` for signing by a validator. + fn produce_attestation_data( &mut self, ctx: RpcContext, - req: ProduceAttestationRequest, - sink: UnarySink, + req: ProduceAttestationDataRequest, + sink: UnarySink, ) { - println!("producing attestation at slot {}", req.get_slot()); + info!(&self.log, "Attempting to produce 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()); + // get the chain spec & state + let spec = self.chain.get_spec(); + let state = self.chain.get_state(); - let mut resp = ProduceAttestationResponse::new(); - resp.set_attestation_data(attestation); + // Start by performing some checks + // Check that the the AttestionData is for the current slot (otherwise it will not be valid) + if req.get_slot() != state.slot { + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::OutOfRange, + "AttestationData request for a slot that is not the current slot." + )) + .map_err(move |e| error!(&self.log, "Failed to reply with failure {:?}: {:?}", req, e)); + } + + // Then collect the data we need for the AttesatationData object + //let beacon_block_root = state.latest_block_roots.first().ok_or_else(|e| ) + + // And finally build the AttestationData object + let mut attestation_data = AttestationDataProto::new(); + attestation_data.set_slot(state.slot.as_u64()); + attestation_data.set_shard(spec.genesis_start_shard); + attestation_data.set_beacon_block_root(b"cats".to_vec()); + //attestation_data. + + let mut resp = ProduceAttestationDataResponse::new(); + resp.set_attestation_data(attestation_data); let f = sink .success(resp) - .map_err(move |e| println!("failed to reply {:?}: {:?}", req, e)); + .map_err(move |e| error!("Failed to reply with success {:?}: {:?}", req, e)); ctx.spawn(f) } - /// Accept some fully-formed `BeaconBlock`, process and publish it. + /// Accept some fully-formed `FreeAttestation` from the validator, + /// store it, and aggregate it into an `Attestation`. fn publish_attestation( &mut self, ctx: RpcContext, diff --git a/eth2/attester/src/lib.rs b/eth2/attester/src/lib.rs index 065fdc9231..3f13555e3d 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(slot, shard)? { + let attestation_data = match self.beacon_node.produce_attestation_data(slot, shard)? { Some(attestation_data) => attestation_data, None => return Ok(PollOutcome::BeaconNodeUnableToProduceAttestation(slot)), }; diff --git a/eth2/attester/src/test_utils/simulated_beacon_node.rs b/eth2/attester/src/test_utils/simulated_beacon_node.rs index 84a203cdb5..d19f434223 100644 --- a/eth2/attester/src/test_utils/simulated_beacon_node.rs +++ b/eth2/attester/src/test_utils/simulated_beacon_node.rs @@ -26,7 +26,7 @@ impl SimulatedBeaconNode { } impl BeaconNode for SimulatedBeaconNode { - fn produce_attestation(&self, slot: Slot, shard: u64) -> ProduceResult { + fn produce_attestation_data(&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(), diff --git a/eth2/attester/src/traits.rs b/eth2/attester/src/traits.rs index 749c6e1a2a..2fd6940af2 100644 --- a/eth2/attester/src/traits.rs +++ b/eth2/attester/src/traits.rs @@ -14,7 +14,7 @@ pub enum PublishOutcome { /// Defines the methods required to produce and publish blocks on a Beacon Node. pub trait BeaconNode: Send + Sync { - fn produce_attestation( + fn produce_attestation_data( &self, slot: Slot, shard: u64, diff --git a/protos/src/services.proto b/protos/src/services.proto index 80d512c54a..35b9c32af1 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -33,7 +33,7 @@ service ValidatorService { /// Service that handles validator attestations service AttestationService { - rpc ProduceAttestation(ProduceAttestationRequest) returns (ProduceAttestationResponse); + rpc ProduceAttestation(ProduceAttestationDataRequest) returns (ProduceAttestationDataResponse); rpc PublishAttestation(PublishAttestationRequest) returns (PublishAttestationResponse); } @@ -138,13 +138,13 @@ message ProposeBlockSlotResponse { * Attestation Service Messages */ -message ProduceAttestationRequest { +message ProduceAttestationDataRequest { uint64 slot = 1; uint64 shard = 2; } -message ProduceAttestationResponse { - Attestation attestation_data = 1; +message ProduceAttestationDataResponse { + AttestationData attestation_data = 1; } message PublishAttestationRequest { @@ -162,7 +162,7 @@ message Crosslink { } -message Attestation { +message AttestationData { uint64 slot = 1; uint64 shard = 2; bytes beacon_block_root = 3; @@ -175,7 +175,7 @@ message Attestation { } message FreeAttestation { - Attestation attestation_data = 1; + AttestationData 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 5a4701ba93..f55acc4f71 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::ProduceAttestationRequest; +use protos::services::ProduceAttestationDataRequest; use types::{AttestationData, FreeAttestation, Slot}; pub struct AttestationGrpcClient { @@ -16,12 +16,12 @@ impl AttestationGrpcClient { } impl BeaconNode for AttestationGrpcClient { - fn produce_attestation( + fn produce_attestation_data( &self, slot: Slot, shard: u64, ) -> Result, BeaconNodeError> { - let mut req = ProduceAttestationRequest::new(); + let mut req = ProduceAttestationDataRequest::new(); req.set_slot(slot.as_u64()); req.set_shard(shard); From c9a7977d6998183458ab7a5d365ac08d8fa69257 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Wed, 27 Mar 2019 14:30:09 +1100 Subject: [PATCH 02/57] Renamed some functions, trying to get beaconnode attestation stuff to work. --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/rpc/src/beacon_attester.rs | 17 +++++++---------- protos/src/services.proto | 2 +- .../attester_service/attestation_grpc_client.rs | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 61b5fb58b4..110d1a99df 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -280,7 +280,7 @@ where } /// Produce an `AttestationData` that is valid for the present `slot` and given `shard`. - pub fn produce_attestation(&self, shard: u64) -> Result { + pub fn produce_attestation_data(&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( diff --git a/beacon_node/rpc/src/beacon_attester.rs b/beacon_node/rpc/src/beacon_attester.rs index 88caacdd0b..4166c30d4d 100644 --- a/beacon_node/rpc/src/beacon_attester.rs +++ b/beacon_node/rpc/src/beacon_attester.rs @@ -9,6 +9,8 @@ use protos::services::{ use protos::services_grpc::BeaconBlockService; use slog::{Logger, info, warn, error}; +const TEST_SHARD_PHASE_ZERO: u8 = 0; + #[derive(Clone)] pub struct AttestationServiceInstance { pub chain: Arc, @@ -29,9 +31,11 @@ impl AttestationService for AttestationServiceInstance { let spec = self.chain.get_spec(); let state = self.chain.get_state(); + let slot_requested = req.get_slot(); + // Start by performing some checks // Check that the the AttestionData is for the current slot (otherwise it will not be valid) - if req.get_slot() != state.slot { + if slot_requested != state.slot { let f = sink .fail(RpcStatus::new( RpcStatusCode::OutOfRange, @@ -40,15 +44,8 @@ impl AttestationService for AttestationServiceInstance { .map_err(move |e| error!(&self.log, "Failed to reply with failure {:?}: {:?}", req, e)); } - // Then collect the data we need for the AttesatationData object - //let beacon_block_root = state.latest_block_roots.first().ok_or_else(|e| ) - - // And finally build the AttestationData object - let mut attestation_data = AttestationDataProto::new(); - attestation_data.set_slot(state.slot.as_u64()); - attestation_data.set_shard(spec.genesis_start_shard); - attestation_data.set_beacon_block_root(b"cats".to_vec()); - //attestation_data. + // Then get the AttestationData from the beacon chain (for shard 0 for now) + let attestation_data = self.chain.produce_attestation_data(TEST_SHARD_PHASE_ZERO); let mut resp = ProduceAttestationDataResponse::new(); resp.set_attestation_data(attestation_data); diff --git a/protos/src/services.proto b/protos/src/services.proto index 35b9c32af1..1dfb53c06c 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -33,7 +33,7 @@ service ValidatorService { /// Service that handles validator attestations service AttestationService { - rpc ProduceAttestation(ProduceAttestationDataRequest) returns (ProduceAttestationDataResponse); + rpc ProduceAttestationData(ProduceAttestationDataRequest) returns (ProduceAttestationDataResponse); rpc PublishAttestation(PublishAttestationRequest) returns (PublishAttestationResponse); } diff --git a/validator_client/src/attester_service/attestation_grpc_client.rs b/validator_client/src/attester_service/attestation_grpc_client.rs index f55acc4f71..bfb7f67c60 100644 --- a/validator_client/src/attester_service/attestation_grpc_client.rs +++ b/validator_client/src/attester_service/attestation_grpc_client.rs @@ -27,7 +27,7 @@ impl BeaconNode for AttestationGrpcClient { let reply = self .client - .produce_attestation(&req) + .produce_attestation_data(&req) .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; // TODO: return correct Attestation From bda381a2648fa21d420b14703bffa55539fc44d7 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Thu, 28 Mar 2019 09:38:39 +1100 Subject: [PATCH 03/57] More progress towards getting the attester working. --- eth2/attester/src/lib.rs | 2 + validator_client/src/service.rs | 164 +++++++++++++++++--------------- 2 files changed, 87 insertions(+), 79 deletions(-) diff --git a/eth2/attester/src/lib.rs b/eth2/attester/src/lib.rs index 3f13555e3d..7b1d261456 100644 --- a/eth2/attester/src/lib.rs +++ b/eth2/attester/src/lib.rs @@ -99,6 +99,8 @@ impl Attester return Ok(PollOutcome::BeaconNodeUnableToProduceAttestation(slot)), }; + dbg!(&attestation_data); + if !self.safe_to_produce(&attestation_data) { return Ok(PollOutcome::SlashableAttestationNotProduced(slot)); } diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 8a7e90d104..4c6a499973 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -25,9 +25,12 @@ use tokio::runtime::Builder; use tokio::timer::Interval; use tokio_timer::clock::Clock; use types::{Epoch, Fork, Slot}; +use std::thread; //TODO: This service should be simplified in the future. Can be made more steamlined. +const POLL_INTERVAL_MILLIS: u64 = 100; + /// The validator service. This is the main thread that executes and maintains validator /// duties. pub struct Service { @@ -217,7 +220,10 @@ impl Service { // TODO: keypairs are randomly generated; they should be loaded from a file or generated. // https://github.com/sigp/lighthouse/issues/160 - let keypairs = Arc::new(vec![Keypair::random()]); + let keypairs = match config.fetch_keys(&log) { + Some(kps) => kps, + None => panic!("No key pairs found, cannot start validator client without. Try running ./account_manager generate first.") + }; // build requisite objects to pass to core thread. let duties_map = Arc::new(EpochDutiesMap::new(config.spec.slots_per_epoch)); @@ -272,87 +278,87 @@ impl Service { Ok(()) })) .map_err(|e| format!("Service thread failed: {:?}", e))?; + + let mut threads = vec![]; + + for keypair in keypairs { + info!(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 slot_clock = service.slot_clock.clone(); + let log = log.clone(); + let attester_grpc_client = Arc::new(AttestationGrpcClient::new(attester_client.clone())); + thread::spawn(move || { + let attester = Attester::new(epoch_map_for_attester, slot_clock, attester_grpc_client, signer); + let mut attester_service = AttesterService { + attester, + poll_interval_millis, + log, + }; + + attester_service.run(); + }) + }; + + //threads.push((duties_manager_thread, producer_thread, attester_thread)); + threads.push((attester_thread)); + } + 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; From ba71e8adca2a6f1e899492bdcd776dc45f981cc3 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Thu, 28 Mar 2019 20:55:07 +1100 Subject: [PATCH 04/57] Merged age-validator-client into luke's changes on validator_client, and fixed all the merge conflicts. --- Jenkinsfile | 22 ++- beacon_node/Cargo.toml | 1 + beacon_node/beacon_chain/src/beacon_chain.rs | 23 +++ beacon_node/beacon_chain/src/initialise.rs | 20 +- beacon_node/client/Cargo.toml | 1 + beacon_node/client/src/lib.rs | 101 +++++++++- beacon_node/client/src/notifier.rs | 4 +- beacon_node/eth2-libp2p/src/behaviour.rs | 33 +++- beacon_node/network/src/sync/simple_sync.rs | 8 +- beacon_node/rpc/src/beacon_block.rs | 110 ++++++++--- beacon_node/rpc/src/beacon_chain.rs | 13 +- beacon_node/rpc/src/beacon_node.rs | 7 +- beacon_node/rpc/src/lib.rs | 1 + beacon_node/rpc/src/validator.rs | 135 ++++++++------ beacon_node/src/run.rs | 2 + eth2/attester/src/lib.rs | 3 +- .../src/per_slot_processing.rs | 1 - .../testing_beacon_state_builder.rs | 4 +- eth2/utils/slot_clock/src/lib.rs | 3 + .../slot_clock/src/system_time_slot_clock.rs | 39 +++- .../slot_clock/src/testing_slot_clock.rs | 6 + protos/src/services.proto | 13 +- .../beacon_block_grpc_client.rs | 38 +--- validator_client/src/config.rs | 3 +- validator_client/src/duties/grpc.rs | 7 +- validator_client/src/duties/mod.rs | 23 ++- validator_client/src/main.rs | 4 +- validator_client/src/service.rs | 173 ++++++++---------- 28 files changed, 540 insertions(+), 258 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 42755d5f7f..1a3afad87f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,20 +1,22 @@ pipeline { - agent { + agent { dockerfile { filename 'Dockerfile' args '-v cargo-cache:/cargocache:rw -e "CARGO_HOME=/cargocache"' } } - stages { - stage('Build') { - steps { - sh 'cargo build' - } - } - stage('Test') { + stages { + stage('Build') { steps { - sh 'cargo test --all' + sh 'cargo build --verbose --all' + sh 'cargo build --verbose --all --release' } } - } + stage('Test') { + steps { + sh 'cargo test --verbose --all' + sh 'cargo test --verbose --all --release' + } + } + } } diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index e7aaf938de..a090c1cc54 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -14,6 +14,7 @@ slog-term = "^2.4.0" slog-async = "^2.3.0" ctrlc = { version = "3.1.1", features = ["termination"] } tokio = "0.1.15" +tokio-timer = "0.2.10" futures = "0.1.25" exit-future = "0.1.3" state_processing = { path = "../eth2/state_processing" } diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9323d1334e..a9a28ea397 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -342,8 +342,17 @@ where // If required, transition the new state to the present slot. for _ in state.slot.as_u64()..present_slot.as_u64() { + // Ensure the next epoch state caches are built in case of an epoch transition. + state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &self.spec)?; + state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &self.spec)?; + per_slot_processing(&mut *state, &latest_block_header, &self.spec)?; } + state.build_epoch_cache(RelativeEpoch::Previous, &self.spec)?; + state.build_epoch_cache(RelativeEpoch::Current, &self.spec)?; + state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &self.spec)?; + state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &self.spec)?; + state.update_pubkey_cache()?; Ok(()) } @@ -405,6 +414,20 @@ where } } + /// Reads the slot clock (see `self.read_slot_clock()` and returns the number of slots since + /// genesis. + pub fn slots_since_genesis(&self) -> Option { + let now = self.read_slot_clock()?; + + if now < self.spec.genesis_slot { + None + } else { + Some(SlotHeight::from( + now.as_u64() - self.spec.genesis_slot.as_u64(), + )) + } + } + /// Returns slot of the present state. /// /// This is distinct to `read_slot_clock`, which reads from the actual system clock. If diff --git a/beacon_node/beacon_chain/src/initialise.rs b/beacon_node/beacon_chain/src/initialise.rs index 7d3c87965f..0951e06fbb 100644 --- a/beacon_node/beacon_chain/src/initialise.rs +++ b/beacon_node/beacon_chain/src/initialise.rs @@ -28,15 +28,19 @@ pub fn initialise_beacon_chain( let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); - let state_builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec); + let state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(8, &spec); let (genesis_state, _keypairs) = state_builder.build(); let mut genesis_block = BeaconBlock::empty(&spec); genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); // Slot clock - let slot_clock = SystemTimeSlotClock::new(genesis_state.genesis_time, spec.seconds_per_slot) - .expect("Unable to load SystemTimeSlotClock"); + let slot_clock = SystemTimeSlotClock::new( + spec.genesis_slot, + genesis_state.genesis_time, + spec.seconds_per_slot, + ) + .expect("Unable to load SystemTimeSlotClock"); // Choose the fork choice let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone()); @@ -65,15 +69,19 @@ pub fn initialise_test_beacon_chain( let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); - let state_builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, spec); + let state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(8, spec); let (genesis_state, _keypairs) = state_builder.build(); let mut genesis_block = BeaconBlock::empty(spec); genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); // Slot clock - let slot_clock = SystemTimeSlotClock::new(genesis_state.genesis_time, spec.seconds_per_slot) - .expect("Unable to load SystemTimeSlotClock"); + let slot_clock = SystemTimeSlotClock::new( + spec.genesis_slot, + genesis_state.genesis_time, + spec.seconds_per_slot, + ) + .expect("Unable to load SystemTimeSlotClock"); // Choose the fork choice let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone()); diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 12c1b5c802..8956dbb07d 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -14,6 +14,7 @@ types = { path = "../../eth2/types" } slot_clock = { path = "../../eth2/utils/slot_clock" } error-chain = "0.12.0" slog = "^2.2.3" +ssz = { path = "../../eth2/utils/ssz" } tokio = "0.1.15" clap = "2.32.0" dirs = "1.0.3" diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index b24d2cb7f2..807fd9301e 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -8,12 +8,20 @@ pub mod notifier; use beacon_chain::BeaconChain; pub use client_config::ClientConfig; pub use client_types::ClientTypes; +use db::ClientDB; use exit_future::Signal; +use fork_choice::ForkChoice; +use futures::{future::Future, Stream}; use network::Service as NetworkService; -use slog::o; +use slog::{error, info, o}; +use slot_clock::SlotClock; +use ssz::TreeHash; use std::marker::PhantomData; use std::sync::Arc; +use std::time::{Duration, Instant}; use tokio::runtime::TaskExecutor; +use tokio::timer::Interval; +use types::Hash256; /// Main beacon node client service. This provides the connection and initialisation of the clients /// sub-services in multiple threads. @@ -26,6 +34,8 @@ pub struct Client { pub network: Arc, /// Signal to terminate the RPC server. pub rpc_exit_signal: Option, + /// Signal to terminate the slot timer. + pub slot_timer_exit_signal: Option, /// The clients logger. log: slog::Logger, /// Marker to pin the beacon chain generics. @@ -42,6 +52,35 @@ impl Client { // generate a beacon chain let beacon_chain = TClientType::initialise_beacon_chain(&config); + if beacon_chain.read_slot_clock().is_none() { + panic!("Cannot start client before genesis!") + } + + // Block starting the client until we have caught the state up to the current slot. + // + // If we don't block here we create an initial scenario where we're unable to process any + // blocks and we're basically useless. + { + let state_slot = beacon_chain.state.read().slot; + let wall_clock_slot = beacon_chain.read_slot_clock().unwrap(); + let slots_since_genesis = beacon_chain.slots_since_genesis().unwrap(); + info!( + log, + "Initializing state"; + "state_slot" => state_slot, + "wall_clock_slot" => wall_clock_slot, + "slots_since_genesis" => slots_since_genesis, + "catchup_distance" => wall_clock_slot - state_slot, + ); + } + do_state_catchup(&beacon_chain, &log); + info!( + log, + "State initialized"; + "state_slot" => beacon_chain.state.read().slot, + "wall_clock_slot" => beacon_chain.read_slot_clock().unwrap(), + ); + // Start the network service, libp2p and syncing threads // TODO: Add beacon_chain reference to network parameters let network_config = &config.net_conf; @@ -65,13 +104,73 @@ impl Client { )); } + let (slot_timer_exit_signal, exit) = exit_future::signal(); + if let Ok(Some(duration_to_next_slot)) = beacon_chain.slot_clock.duration_to_next_slot() { + // set up the validator work interval - start at next slot and proceed every slot + 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() + duration_to_next_slot, slot_duration) + }; + + let chain = beacon_chain.clone(); + let log = log.new(o!("Service" => "SlotTimer")); + executor.spawn( + exit.until( + interval + .for_each(move |_| { + do_state_catchup(&chain, &log); + + Ok(()) + }) + .map_err(|_| ()), + ) + .map(|_| ()), + ); + } + Ok(Client { config, beacon_chain, rpc_exit_signal, + slot_timer_exit_signal: Some(slot_timer_exit_signal), log, network, phantom: PhantomData, }) } } + +fn do_state_catchup(chain: &Arc>, log: &slog::Logger) +where + T: ClientDB, + U: SlotClock, + F: ForkChoice, +{ + if let Some(genesis_height) = chain.slots_since_genesis() { + let result = chain.catchup_state(); + + let common = o!( + "best_slot" => chain.head().beacon_block.slot, + "latest_block_root" => format!("{}", chain.head().beacon_block_root), + "wall_clock_slot" => chain.read_slot_clock().unwrap(), + "state_slot" => chain.state.read().slot, + "slots_since_genesis" => genesis_height, + ); + + match result { + Ok(_) => info!( + log, + "NewSlot"; + common + ), + Err(e) => error!( + log, + "StateCatchupFailed"; + "error" => format!("{:?}", e), + common + ), + }; + } +} diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 335183c7de..91a9f3a261 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -2,7 +2,7 @@ use crate::Client; use crate::ClientTypes; use exit_future::Exit; use futures::{Future, Stream}; -use slog::{debug, info, o}; +use slog::{debug, o}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use tokio::runtime::TaskExecutor; @@ -22,7 +22,7 @@ pub fn run(client: &Client, executor: TaskExecutor, exit: Exi // build heartbeat logic here let heartbeat = move |_| { - info!(log, "Temp heartbeat output"); + debug!(log, "Temp heartbeat output"); //TODO: Remove this logic. Testing only let mut count = counter.lock().unwrap(); *count += 1; diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 41b7c89657..2865971838 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -49,12 +49,15 @@ impl NetworkBehaviourEventProcess { + debug!(self.log, "Received GossipEvent"; "msg" => format!("{:?}", 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 + "Received undecodable message from Peer {:?} error", gs_msg.source; + "error" => format!("{:?}", e) ); return; } @@ -192,7 +195,7 @@ pub enum BehaviourEvent { } /// Messages that are passed to and from the pubsub (Gossipsub) behaviour. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum PubsubMessage { /// Gossipsub message providing notification of a new block. Block(BlockRootSlot), @@ -220,11 +223,11 @@ 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 => { + 0 => { let (block, index) = BlockRootSlot::ssz_decode(bytes, index)?; Ok((PubsubMessage::Block(block), index)) } - 2 => { + 1 => { let (attestation, index) = Attestation::ssz_decode(bytes, index)?; Ok((PubsubMessage::Attestation(attestation), index)) } @@ -232,3 +235,25 @@ impl Decodable for PubsubMessage { } } } + +#[cfg(test)] +mod test { + use super::*; + use types::*; + + #[test] + fn ssz_encoding() { + let original = PubsubMessage::Block(BlockRootSlot { + block_root: Hash256::from_slice(&[42; 32]), + slot: Slot::new(4), + }); + + let encoded = ssz_encode(&original); + + println!("{:?}", encoded); + + let (decoded, _i) = PubsubMessage::ssz_decode(&encoded, 0).unwrap(); + + assert_eq!(original, decoded); + } +} diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 2aa0a1d7dd..85949fa98e 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -523,9 +523,9 @@ impl SimpleSync { msg: BlockRootSlot, network: &mut NetworkContext, ) { - debug!( + info!( self.log, - "BlockSlot"; + "NewGossipBlock"; "peer" => format!("{:?}", peer_id), ); // TODO: filter out messages that a prior to the finalized slot. @@ -557,9 +557,9 @@ impl SimpleSync { msg: Attestation, _network: &mut NetworkContext, ) { - debug!( + info!( self.log, - "Attestation"; + "NewAttestationGossip"; "peer" => format!("{:?}", peer_id), ); diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index 4e1875665b..f6b426c18f 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -1,3 +1,4 @@ +use crate::beacon_chain::BeaconChain; use crossbeam_channel; use eth2_libp2p::rpc::methods::BlockRootSlot; use eth2_libp2p::PubsubMessage; @@ -10,10 +11,14 @@ use protos::services::{ }; use protos::services_grpc::BeaconBlockService; use slog::Logger; -use types::{Hash256, Slot}; +use slog::{debug, error, info, warn}; +use ssz::{Decodable, TreeHash}; +use std::sync::Arc; +use types::{BeaconBlock, Hash256, Slot}; #[derive(Clone)] pub struct BeaconBlockServiceInstance { + pub chain: Arc, pub network_chan: crossbeam_channel::Sender, pub log: Logger, } @@ -30,8 +35,7 @@ impl BeaconBlockService for BeaconBlockServiceInstance { // TODO: build a legit block. let mut block = BeaconBlockProto::new(); - block.set_slot(req.get_slot()); - block.set_block_root(b"cats".to_vec()); + block.set_ssz(b"cats".to_vec()); let mut resp = ProduceBeaconBlockResponse::new(); resp.set_block(block); @@ -49,26 +53,88 @@ impl BeaconBlockService for BeaconBlockServiceInstance { req: PublishBeaconBlockRequest, sink: UnarySink, ) { - let block = req.get_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 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"); - self.network_chan.send(NetworkMessage::Publish { - topics: vec![topic], - message, - }); - - // TODO: actually process the block. let mut resp = PublishBeaconBlockResponse::new(); - resp.set_success(true); + + let ssz_serialized_block = req.get_block().get_ssz(); + + match BeaconBlock::ssz_decode(ssz_serialized_block, 0) { + Ok((block, _i)) => { + let block_root = Hash256::from_slice(&block.hash_tree_root()[..]); + + match self.chain.process_block(block.clone()) { + Ok(outcome) => { + if outcome.sucessfully_processed() { + // Block was successfully processed. + info!( + self.log, + "PublishBeaconBlock"; + "type" => "valid_block", + "block_slot" => block.slot, + "outcome" => format!("{:?}", outcome) + ); + + // TODO: Obtain topics from the network service properly. + let topic = + types::TopicBuilder::new("beacon_chain".to_string()).build(); + let message = PubsubMessage::Block(BlockRootSlot { + block_root, + slot: block.slot, + }); + + println!("Sending beacon block to gossipsub"); + self.network_chan.send(NetworkMessage::Publish { + topics: vec![topic], + message, + }); + + resp.set_success(true); + } else if outcome.is_invalid() { + // Block was invalid. + warn!( + self.log, + "PublishBeaconBlock"; + "type" => "invalid_block", + "outcome" => format!("{:?}", outcome) + ); + + resp.set_success(false); + resp.set_msg( + format!("InvalidBlock: {:?}", outcome).as_bytes().to_vec(), + ); + } else { + // Some failure during processing. + warn!( + self.log, + "PublishBeaconBlock"; + "type" => "unable_to_import", + "outcome" => format!("{:?}", outcome) + ); + + resp.set_success(false); + resp.set_msg(format!("other: {:?}", outcome).as_bytes().to_vec()); + } + } + Err(e) => { + // Some failure during processing. + error!( + self.log, + "PublishBeaconBlock"; + "type" => "failed_to_process", + "error" => format!("{:?}", e) + ); + + resp.set_success(false); + resp.set_msg(format!("failed_to_process: {:?}", e).as_bytes().to_vec()); + } + } + + resp.set_success(true); + } + Err(_) => { + resp.set_success(false); + resp.set_msg(b"Invalid SSZ".to_vec()); + } + }; let f = sink .success(resp) diff --git a/beacon_node/rpc/src/beacon_chain.rs b/beacon_node/rpc/src/beacon_chain.rs index 9b26818767..0551a80246 100644 --- a/beacon_node/rpc/src/beacon_chain.rs +++ b/beacon_node/rpc/src/beacon_chain.rs @@ -5,14 +5,18 @@ use beacon_chain::{ parking_lot::RwLockReadGuard, slot_clock::SlotClock, types::{BeaconState, ChainSpec}, - CheckPoint, }; +pub use beacon_chain::{BeaconChainError, BlockProcessingOutcome}; +use types::BeaconBlock; /// The RPC's API to the beacon chain. pub trait BeaconChain: Send + Sync { fn get_spec(&self) -> &ChainSpec; fn get_state(&self) -> RwLockReadGuard; + + fn process_block(&self, block: BeaconBlock) + -> Result; } impl BeaconChain for RawBeaconChain @@ -28,4 +32,11 @@ where fn get_state(&self) -> RwLockReadGuard { self.state.read() } + + fn process_block( + &self, + block: BeaconBlock, + ) -> Result { + self.process_block(block) + } } diff --git a/beacon_node/rpc/src/beacon_node.rs b/beacon_node/rpc/src/beacon_node.rs index 7b00f04f48..c92ab66162 100644 --- a/beacon_node/rpc/src/beacon_node.rs +++ b/beacon_node/rpc/src/beacon_node.rs @@ -1,7 +1,7 @@ use crate::beacon_chain::BeaconChain; use futures::Future; use grpcio::{RpcContext, UnarySink}; -use protos::services::{Empty, Fork, NodeInfo}; +use protos::services::{Empty, Fork, NodeInfoResponse}; use protos::services_grpc::BeaconNodeService; use slog::{trace, warn}; use std::sync::Arc; @@ -14,11 +14,11 @@ pub struct BeaconNodeServiceInstance { impl BeaconNodeService for BeaconNodeServiceInstance { /// Provides basic node information. - fn info(&mut self, ctx: RpcContext, _req: Empty, sink: UnarySink) { + 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(); + let mut node_info = NodeInfoResponse::new(); node_info.set_version(version::version()); // get the chain state @@ -34,6 +34,7 @@ impl BeaconNodeService for BeaconNodeServiceInstance { node_info.set_fork(fork); node_info.set_genesis_time(genesis_time); + node_info.set_genesis_slot(self.chain.get_spec().genesis_slot.as_u64()); node_info.set_chain_id(self.chain.get_spec().chain_id as u32); // send the node_info the requester diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index 20cd62b1df..2d47b4a69a 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -43,6 +43,7 @@ pub fn start_server( let beacon_block_service = { let instance = BeaconBlockServiceInstance { + chain: beacon_chain.clone(), network_chan, log: log.clone(), }; diff --git a/beacon_node/rpc/src/validator.rs b/beacon_node/rpc/src/validator.rs index 47886a9df6..936c95f52f 100644 --- a/beacon_node/rpc/src/validator.rs +++ b/beacon_node/rpc/src/validator.rs @@ -4,9 +4,10 @@ use futures::Future; use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink}; use protos::services::{ActiveValidator, GetDutiesRequest, GetDutiesResponse, ValidatorDuty}; use protos::services_grpc::ValidatorService; -use slog::{debug, Logger}; +use slog::{debug, info, warn, Logger}; use ssz::Decodable; use std::sync::Arc; +use types::{Epoch, RelativeEpoch}; #[derive(Clone)] pub struct ValidatorServiceInstance { @@ -28,23 +29,47 @@ impl ValidatorService for ValidatorServiceInstance { let validators = req.get_validators(); debug!(self.log, "RPC request"; "endpoint" => "GetValidatorDuties", "epoch" => req.get_epoch()); - let epoch = req.get_epoch(); + let epoch = Epoch::from(req.get_epoch()); let mut resp = GetDutiesResponse::new(); let resp_validators = resp.mut_active_validators(); let spec = self.chain.get_spec(); let state = self.chain.get_state(); - //TODO: Decide whether to rebuild the cache - //TODO: Get the active validator indicies - //let active_validator_indices = self.chain.state.read().get_cached_active_validator_indices( - let active_validator_indices = vec![1, 2, 3, 4, 5, 6, 7, 8]; - // TODO: Is this the most efficient? Perhaps we cache this data structure. + let relative_epoch = + match RelativeEpoch::from_epoch(state.slot.epoch(spec.slots_per_epoch), epoch) { + Ok(v) => v, + Err(e) => { + // incorrect epoch + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::FailedPrecondition, + Some(format!("Invalid epoch: {:?}", e)), + )) + .map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e)); + return ctx.spawn(f); + } + }; - // this is an array of validators who are to propose this epoch - // TODO: RelativeEpoch? - //let validator_proposers = [0..spec.slots_per_epoch].iter().map(|slot| state.get_beacon_proposer_index(Slot::from(slot), epoch, &spec)).collect(); - let validator_proposers: Vec = vec![1, 2, 3, 4, 5]; + let validator_proposers: Result, _> = epoch + .slot_iter(spec.slots_per_epoch) + .map(|slot| state.get_beacon_proposer_index(slot, relative_epoch, &spec)) + .collect(); + let validator_proposers = match validator_proposers { + Ok(v) => v, + Err(e) => { + // could not get the validator proposer index + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::FailedPrecondition, + Some(format!("Could not find beacon proposers: {:?}", e)), + )) + .map_err(move |e| warn!(log_clone, "failed to reply {:?} : {:?}", req, e)); + return ctx.spawn(f); + } + }; // get the duties for each validator for validator_pk in validators.get_public_keys() { @@ -53,45 +78,65 @@ impl ValidatorService for ValidatorServiceInstance { let public_key = match PublicKey::ssz_decode(validator_pk, 0) { Ok((v, _index)) => v, Err(_) => { + let log_clone = self.log.clone(); let f = sink .fail(RpcStatus::new( RpcStatusCode::InvalidArgument, Some("Invalid public_key".to_string()), )) - //TODO: Handle error correctly - .map_err(move |e| println!("failed to reply {:?}: {:?}", req, e)); + .map_err(move |e| warn!(log_clone, "failed to reply {:?}", req)); return ctx.spawn(f); } }; - // is the validator active + // get the validator index let val_index = match state.get_validator_index(&public_key) { - Ok(Some(index)) => { - if active_validator_indices.contains(&index) { - // validator is active, return the index - index - } else { - // validator is inactive, go to the next validator - active_validator.set_none(false); - resp_validators.push(active_validator); - break; - } - } - // validator index is not known, skip it - Ok(_) => { + Ok(Some(index)) => index, + Ok(None) => { + // index not present in registry, set the duties for this key to None + warn!( + self.log, + "RPC requested a public key that is not in the registry: {:?}", public_key + ); active_validator.set_none(false); resp_validators.push(active_validator); - break; + continue; } // the cache is not built, throw an error - Err(_) => { + Err(e) => { + let log_clone = self.log.clone(); let f = sink .fail(RpcStatus::new( RpcStatusCode::FailedPrecondition, - Some("Beacon state cache is not built".to_string()), + Some(format!("Beacon state error {:?}", e)), )) - //TODO: Handle error correctly - .map_err(move |e| println!("failed to reply {:?}: {:?}", req, e)); + .map_err(move |e| warn!(log_clone, "Failed to reply {:?}: {:?}", req, e)); + return ctx.spawn(f); + } + }; + + // get attestation duties and check if validator is active + let attestation_duties = match state.get_attestation_duties(val_index, &spec) { + Ok(Some(v)) => v, + Ok(_) => { + // validator is inactive, go to the next validator + warn!( + self.log, + "RPC requested an inactive validator key: {:?}", public_key + ); + active_validator.set_none(false); + resp_validators.push(active_validator); + continue; + } + // the cache is not built, throw an error + Err(e) => { + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::FailedPrecondition, + Some(format!("Beacon state error {:?}", e)), + )) + .map_err(move |e| warn!(log_clone, "Failed to reply {:?}: {:?}", req, e)); return ctx.spawn(f); } }; @@ -100,33 +145,15 @@ impl ValidatorService for ValidatorServiceInstance { let mut duty = ValidatorDuty::new(); // check if the validator needs to propose a block - if let Some(slot) = validator_proposers - .iter() - .position(|&v| val_index as u64 == v) - { - duty.set_block_production_slot(epoch * spec.slots_per_epoch + slot as u64); + if let Some(slot) = validator_proposers.iter().position(|&v| val_index == v) { + duty.set_block_production_slot( + epoch.start_slot(spec.slots_per_epoch).as_u64() + slot as u64, + ); } else { // no blocks to propose this epoch duty.set_none(false) } - // get attestation duties - let attestation_duties = match state.get_attestation_duties(val_index, &spec) { - Ok(Some(v)) => v, - Ok(_) => unreachable!(), //we've checked the validator index - // the cache is not built, throw an error - Err(_) => { - let f = sink - .fail(RpcStatus::new( - RpcStatusCode::FailedPrecondition, - Some("Beacon state cache is not built".to_string()), - )) - //TODO: Handle error correctly - .map_err(move |e| println!("failed to reply {:?}: {:?}", req, e)); - return ctx.spawn(f); - } - }; - duty.set_committee_index(attestation_duties.committee_index as u64); duty.set_attestation_slot(attestation_duties.slot.as_u64()); duty.set_attestation_shard(attestation_duties.shard); diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index b3b2844526..1d9156124f 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -6,10 +6,12 @@ use futures::Future; use slog::info; use std::cell::RefCell; use tokio::runtime::Builder; +use tokio_timer::clock::Clock; pub fn run_beacon_node(config: ClientConfig, log: &slog::Logger) -> error::Result<()> { let mut runtime = Builder::new() .name_prefix("main-") + .clock(Clock::system()) .build() .map_err(|e| format!("{:?}", e))?; diff --git a/eth2/attester/src/lib.rs b/eth2/attester/src/lib.rs index 8dd83fa041..53d81e8974 100644 --- a/eth2/attester/src/lib.rs +++ b/eth2/attester/src/lib.rs @@ -90,8 +90,7 @@ impl Attester { aggregate_signature: agg_sig, }; - self.beacon_node - .publish_attestation(attestation)?; + self.beacon_node.publish_attestation(attestation)?; Ok(PollOutcome::AttestationProduced(attestation_duty.slot)) } diff --git a/eth2/state_processing/src/per_slot_processing.rs b/eth2/state_processing/src/per_slot_processing.rs index 8f02b70e3a..c6b5312c70 100644 --- a/eth2/state_processing/src/per_slot_processing.rs +++ b/eth2/state_processing/src/per_slot_processing.rs @@ -20,7 +20,6 @@ pub fn per_slot_processing( if (state.slot + 1) % spec.slots_per_epoch == 0 { per_epoch_processing(state, spec)?; - state.advance_caches(); } state.slot += 1; diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 473cd4166f..b38e8b5273 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -120,8 +120,10 @@ impl TestingBeaconStateBuilder { }) .collect(); + let genesis_time = 1553753928; // arbitrary + let mut state = BeaconState::genesis( - 0, + genesis_time, Eth1Data { deposit_root: Hash256::zero(), block_hash: Hash256::zero(), diff --git a/eth2/utils/slot_clock/src/lib.rs b/eth2/utils/slot_clock/src/lib.rs index 0379d50d9a..fd5a2d1d7d 100644 --- a/eth2/utils/slot_clock/src/lib.rs +++ b/eth2/utils/slot_clock/src/lib.rs @@ -3,10 +3,13 @@ mod testing_slot_clock; pub use crate::system_time_slot_clock::{Error as SystemTimeSlotClockError, SystemTimeSlotClock}; pub use crate::testing_slot_clock::{Error as TestingSlotClockError, TestingSlotClock}; +use std::time::Duration; pub use types::Slot; pub trait SlotClock: Send + Sync { type Error; fn present_slot(&self) -> Result, Self::Error>; + + fn duration_to_next_slot(&self) -> Result, Self::Error>; } diff --git a/eth2/utils/slot_clock/src/system_time_slot_clock.rs b/eth2/utils/slot_clock/src/system_time_slot_clock.rs index 99f051985f..4dfc6b37da 100644 --- a/eth2/utils/slot_clock/src/system_time_slot_clock.rs +++ b/eth2/utils/slot_clock/src/system_time_slot_clock.rs @@ -13,6 +13,7 @@ pub enum Error { /// Determines the present slot based upon the present system time. #[derive(Clone)] pub struct SystemTimeSlotClock { + genesis_slot: Slot, genesis_seconds: u64, slot_duration_seconds: u64, } @@ -22,6 +23,7 @@ impl SystemTimeSlotClock { /// /// Returns an Error if `slot_duration_seconds == 0`. pub fn new( + genesis_slot: Slot, genesis_seconds: u64, slot_duration_seconds: u64, ) -> Result { @@ -29,6 +31,7 @@ impl SystemTimeSlotClock { Err(Error::SlotDurationIsZero) } else { Ok(Self { + genesis_slot, genesis_seconds, slot_duration_seconds, }) @@ -44,11 +47,17 @@ impl SlotClock for SystemTimeSlotClock { let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH)?; let duration_since_genesis = duration_since_epoch.checked_sub(Duration::from_secs(self.genesis_seconds)); + match duration_since_genesis { None => Ok(None), - Some(d) => Ok(slot_from_duration(self.slot_duration_seconds, d)), + Some(d) => Ok(slot_from_duration(self.slot_duration_seconds, d) + .and_then(|s| Some(s + self.genesis_slot))), } } + + fn duration_to_next_slot(&self) -> Result, Error> { + duration_to_next_slot(self.genesis_seconds, self.slot_duration_seconds) + } } impl From for Error { @@ -62,6 +71,30 @@ fn slot_from_duration(slot_duration_seconds: u64, duration: Duration) -> Option< duration.as_secs().checked_div(slot_duration_seconds)?, )) } +// calculate the duration to the next slot +fn duration_to_next_slot( + genesis_time: u64, + seconds_per_slot: u64, +) -> Result, Error> { + let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; + let genesis_time = Duration::from_secs(genesis_time); + + if now < genesis_time { + return Ok(None); + } + + let since_genesis = now - genesis_time; + + let elapsed_slots = since_genesis.as_secs() / seconds_per_slot; + + let next_slot_start_seconds = (elapsed_slots + 1) + .checked_mul(seconds_per_slot) + .expect("Next slot time should not overflow u64"); + + let time_to_next_slot = Duration::from_secs(next_slot_start_seconds) - since_genesis; + + Ok(Some(time_to_next_slot)) +} #[cfg(test)] mod tests { @@ -74,6 +107,7 @@ mod tests { #[test] fn test_slot_now() { let slot_time = 100; + let genesis_slot = Slot::new(0); let now = SystemTime::now(); let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap(); @@ -81,18 +115,21 @@ mod tests { let genesis = since_epoch.as_secs() - slot_time * 89; let clock = SystemTimeSlotClock { + genesis_slot, genesis_seconds: genesis, slot_duration_seconds: slot_time, }; assert_eq!(clock.present_slot().unwrap(), Some(Slot::new(89))); let clock = SystemTimeSlotClock { + genesis_slot, genesis_seconds: since_epoch.as_secs(), slot_duration_seconds: slot_time, }; assert_eq!(clock.present_slot().unwrap(), Some(Slot::new(0))); let clock = SystemTimeSlotClock { + genesis_slot, genesis_seconds: since_epoch.as_secs() - slot_time * 42 - 5, slot_duration_seconds: slot_time, }; diff --git a/eth2/utils/slot_clock/src/testing_slot_clock.rs b/eth2/utils/slot_clock/src/testing_slot_clock.rs index 80ee405397..b5c36dfa0a 100644 --- a/eth2/utils/slot_clock/src/testing_slot_clock.rs +++ b/eth2/utils/slot_clock/src/testing_slot_clock.rs @@ -1,5 +1,6 @@ use super::SlotClock; use std::sync::RwLock; +use std::time::Duration; use types::Slot; #[derive(Debug, PartialEq)] @@ -32,6 +33,11 @@ impl SlotClock for TestingSlotClock { let slot = *self.slot.read().expect("TestingSlotClock poisoned."); Ok(Some(Slot::new(slot))) } + + /// Always returns a duration of 1 second. + fn duration_to_next_slot(&self) -> Result, Error> { + Ok(Some(Duration::from_secs(1))) + } } #[cfg(test)] diff --git a/protos/src/services.proto b/protos/src/services.proto index bdb311fd63..b61e5aac0f 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -14,7 +14,7 @@ package ethereum.beacon.rpc.v1; // Service that currently identifies a beacon node service BeaconNodeService { - rpc Info(Empty) returns (NodeInfo); + rpc Info(Empty) returns (NodeInfoResponse); } /// Service that handles block production @@ -40,11 +40,12 @@ service AttestationService { /* * Beacon Node Service Message */ -message NodeInfo { +message NodeInfoResponse { string version = 1; Fork fork = 2; uint32 chain_id = 3; uint64 genesis_time = 4; + uint64 genesis_slot = 5; } message Fork { @@ -53,8 +54,7 @@ message Fork { uint64 epoch = 3; } -message Empty { -} +message Empty {} /* @@ -83,10 +83,7 @@ message PublishBeaconBlockResponse { } message BeaconBlock { - uint64 slot = 1; - bytes block_root = 2; - bytes randao_reveal = 3; - bytes signature = 4; + bytes ssz = 1; } /* diff --git a/validator_client/src/block_producer_service/beacon_block_grpc_client.rs b/validator_client/src/block_producer_service/beacon_block_grpc_client.rs index 04a02a2213..ba2acfffbb 100644 --- a/validator_client/src/block_producer_service/beacon_block_grpc_client.rs +++ b/validator_client/src/block_producer_service/beacon_block_grpc_client.rs @@ -5,7 +5,7 @@ use protos::services::{ use protos::services_grpc::BeaconBlockServiceClient; use ssz::{ssz_encode, Decodable}; use std::sync::Arc; -use types::{BeaconBlock, BeaconBlockBody, Eth1Data, Hash256, Signature, Slot}; +use types::{BeaconBlock, Signature, Slot}; /// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be /// implemented upon it. @@ -40,33 +40,12 @@ impl BeaconNode for BeaconBlockGrpcClient { if reply.has_block() { let block = reply.get_block(); + let ssz = block.get_ssz(); - let (signature, _) = Signature::ssz_decode(block.get_signature(), 0) - .map_err(|_| BeaconNodeError::DecodeFailure)?; + let (block, _i) = + BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| BeaconNodeError::DecodeFailure)?; - let (randao_reveal, _) = Signature::ssz_decode(block.get_randao_reveal(), 0) - .map_err(|_| BeaconNodeError::DecodeFailure)?; - - // TODO: this conversion is incomplete; fix it. - Ok(Some(BeaconBlock { - slot: Slot::new(block.get_slot()), - previous_block_root: Hash256::zero(), - state_root: Hash256::zero(), - signature, - body: BeaconBlockBody { - randao_reveal, - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - }, - proposer_slashings: vec![], - attester_slashings: vec![], - attestations: vec![], - deposits: vec![], - voluntary_exits: vec![], - transfers: vec![], - }, - })) + Ok(Some(block)) } else { Ok(None) } @@ -79,12 +58,11 @@ impl BeaconNode for BeaconBlockGrpcClient { fn publish_beacon_block(&self, block: BeaconBlock) -> Result { let mut req = PublishBeaconBlockRequest::new(); + let ssz = ssz_encode(&block); + // TODO: this conversion is incomplete; fix it. let mut grpc_block = GrpcBeaconBlock::new(); - grpc_block.set_slot(block.slot.as_u64()); - grpc_block.set_block_root(vec![0]); - grpc_block.set_randao_reveal(ssz_encode(&block.body.randao_reveal)); - grpc_block.set_signature(ssz_encode(&block.signature)); + grpc_block.set_ssz(ssz); req.set_block(grpc_block); diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 78900374a2..3d426e8c70 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,6 +1,6 @@ -use clap::ArgMatches; use bincode; use bls::Keypair; +use clap::ArgMatches; use slog::{debug, error, info}; use std::fs; use std::fs::File; @@ -67,6 +67,7 @@ impl Config { config.spec = match spec_str { "foundation" => ChainSpec::foundation(), "few_validators" => ChainSpec::few_validators(), + "lighthouse_testnet" => ChainSpec::lighthouse_testnet(), // Should be impossible due to clap's `possible_values(..)` function. _ => unreachable!(), }; diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index 4bb76e14c6..041d41adde 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -1,9 +1,11 @@ use super::epoch_duties::{EpochDuties, EpochDuty}; use super::traits::{BeaconNode, BeaconNodeError}; +use grpcio::CallOption; use protos::services::{GetDutiesRequest, Validators}; use protos::services_grpc::ValidatorServiceClient; use ssz::ssz_encode; use std::collections::HashMap; +use std::time::Duration; use types::{Epoch, Keypair, Slot}; impl BeaconNode for ValidatorServiceClient { @@ -21,6 +23,9 @@ impl BeaconNode for ValidatorServiceClient { validators.set_public_keys(signers.iter().map(|v| ssz_encode(&v.pk)).collect()); req.set_validators(validators); + // set a timeout for requests + // let call_opt = CallOption::default().timeout(Duration::from_secs(2)); + // send the request, get the duties reply let reply = self .get_validator_duties(&req) @@ -31,7 +36,7 @@ impl BeaconNode for ValidatorServiceClient { if !validator_duty.has_duty() { // validator is inactive epoch_duties.insert(signers[index].clone(), None); - break; + continue; } // active validator let active_duty = validator_duty.get_duty(); diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index e239ca7952..9d92215928 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -52,20 +52,23 @@ impl DutiesManager { /// be a wall-clock (e.g., system time, remote server time, etc.). fn update(&self, epoch: Epoch) -> Result { let duties = self.beacon_node.request_duties(epoch, &self.signers)?; - // If these duties were known, check to see if they're updates or identical. - if let Some(known_duties) = self.duties_map.read()?.get(&epoch) { - if *known_duties == duties { - return Ok(UpdateOutcome::NoChange(epoch)); - } else { - //TODO: Duties could be large here. Remove from display and avoid the clone. - self.duties_map.write()?.insert(epoch, duties.clone()); - return Ok(UpdateOutcome::DutiesChanged(epoch, duties)); + { + // If these duties were known, check to see if they're updates or identical. + if let Some(known_duties) = self.duties_map.read()?.get(&epoch) { + if *known_duties == duties { + return Ok(UpdateOutcome::NoChange(epoch)); + } } - } else { + } + if !self.duties_map.read()?.contains_key(&epoch) { //TODO: Remove clone by removing duties from outcome self.duties_map.write()?.insert(epoch, duties.clone()); return Ok(UpdateOutcome::NewDuties(epoch, duties)); - }; + } + // duties have changed + //TODO: Duties could be large here. Remove from display and avoid the clone. + self.duties_map.write()?.insert(epoch, duties.clone()); + return Ok(UpdateOutcome::DutiesChanged(epoch, duties)); } /// A future wrapping around `update()`. This will perform logic based upon the update diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 84d0cbff7b..d044030fe6 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -43,8 +43,8 @@ fn main() { .short("s") .help("Configuration of Beacon Chain") .takes_value(true) - .possible_values(&["foundation", "few_validators"]) - .default_value("foundation"), + .possible_values(&["foundation", "few_validators", "lighthouse_testnet"]) + .default_value("lighthouse_testnet"), ) .get_matches(); diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index c2caed59df..86f310a06d 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -25,6 +25,7 @@ use tokio::prelude::*; use tokio::runtime::Builder; use tokio::timer::Interval; use tokio_timer::clock::Clock; +use types::test_utils::generate_deterministic_keypairs; use types::{Epoch, Fork, Slot}; use std::thread; @@ -45,8 +46,6 @@ pub struct Service { slot_clock: SystemTimeSlotClock, /// The current slot we are processing. current_slot: Slot, - /// Duration until the next slot. This is used for initializing the tokio timer interval. - duration_to_next_slot: Duration, /// The number of slots per epoch to allow for converting slots to epochs. slots_per_epoch: u64, // GRPC Clients @@ -107,6 +106,7 @@ impl Service { // build requisite objects to form Self let genesis_time = node_info.get_genesis_time(); + let genesis_slot = Slot::from(node_info.get_genesis_slot()); info!(log,"Beacon node connected"; "Node Version" => node_info.version.clone(), "Chain ID" => node_info.chain_id, "Genesis time" => genesis_time); @@ -142,46 +142,21 @@ impl Service { }; // build the validator slot clock - let slot_clock = SystemTimeSlotClock::new(genesis_time, config.spec.seconds_per_slot) - .expect("Unable to instantiate SystemTimeSlotClock."); + let slot_clock = + SystemTimeSlotClock::new(genesis_slot, genesis_time, config.spec.seconds_per_slot) + .expect("Unable to instantiate SystemTimeSlotClock."); 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) - .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") - }; - Ok(Self { connected_node_version: node_info.version, chain_id: node_info.chain_id as u16, fork, slot_clock, current_slot, - duration_to_next_slot, slots_per_epoch: config.spec.slots_per_epoch, beacon_block_client, validator_client, @@ -204,15 +179,18 @@ impl Service { .build() .map_err(|e| format!("Tokio runtime failed: {}", e))?; + let duration_to_next_slot = service + .slot_clock + .duration_to_next_slot() + .map_err(|e| format!("System clock error: {:?}", e))? + .expect("Cannot start before genesis"); + // set up the validator work interval - start at next slot and proceed every slot 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.duration_to_next_slot, - slot_duration, - ) + Interval::new(Instant::now() + duration_to_next_slot, slot_duration) }; /* kick off core service */ @@ -221,11 +199,14 @@ impl Service { // TODO: keypairs are randomly generated; they should be loaded from a file or generated. // https://github.com/sigp/lighthouse/issues/160 + /* In future, load generated keys from disk. let keypairs = match config.fetch_keys(&log.clone()) { Some(kps) => kps, None => panic!("No key pairs found, cannot start validator client without. Try running ./account_manager generate first.") }; + */ + let keypairs = Arc::new(generate_deterministic_keypairs(8)); /* build requisite objects to pass to core thread */ @@ -243,71 +224,75 @@ impl Service { }); // run the core thread - runtime - .block_on(interval.for_each(move |_| { - let log = service.log.clone(); + runtime.block_on( + interval + .for_each(move |_| { + let log = service.log.clone(); - /* get the current slot and epoch */ - let current_slot = match service.slot_clock.present_slot() { - Err(e) => { - error!(log, "SystemTimeError {:?}", e); - return Ok(()); - } - Ok(slot) => slot.expect("Genesis is in the future"), - }; - - let current_epoch = current_slot.epoch(service.slots_per_epoch); - - debug_assert!( - current_slot > service.current_slot, - "The Timer should poll a new slot" - ); - - info!(log, "Processing slot: {}", current_slot.as_u64()); - - /* check for new duties */ - - let cloned_log = log.clone(); - let cloned_manager = manager.clone(); - tokio::spawn(futures::future::poll_fn(move || { - cloned_manager.run_update(current_epoch.clone(), cloned_log.clone()) - })); - - /* execute any specified duties */ - - if let Some(work) = manager.get_current_work(current_slot) { - for (keypair, work_type) in work { - if work_type.produce_block { - // TODO: Produce a beacon block in a new thread + /* get the current slot and epoch */ + let current_slot = match service.slot_clock.present_slot() { + Err(e) => { + error!(log, "SystemTimeError {:?}", e); + return Ok(()); } - if work_type.attestation_duty.is_some() { - // available AttestationDuty info - let attestation_duty = - work_type.attestation_duty.expect("Cannot be None"); - let attester_grpc_client = - Arc::new( - AttestationGrpcClient::new( - service.attester_client.clone() - ) - ); - let signer = Arc::new(AttesterLocalSigner::new(keypair.clone())); - let attester = - Attester::new( - attester_grpc_client, - signer); - let mut attester_service = AttesterService { - attester, - poll_interval_millis: POLL_INTERVAL_MILLIS, - log: log.clone(), - }; - attester_service.run(); + Ok(slot) => slot.expect("Genesis is in the future"), + }; + + let current_epoch = current_slot.epoch(service.slots_per_epoch); + + debug_assert!( + current_slot > service.current_slot, + "The Timer should poll a new slot" + ); + + info!(log, "Processing slot: {}", current_slot.as_u64()); + + /* check for new duties */ + + let cloned_manager = manager.clone(); + let cloned_log = log.clone(); + // spawn a new thread separate to the runtime + std::thread::spawn(move || { + cloned_manager.run_update(current_epoch.clone(), cloned_log.clone()); + dbg!("Finished thread"); + }); + + /* execute any specified duties */ + + if let Some(work) = manager.get_current_work(current_slot) { + for (keypair, work_type) in work { + if work_type.produce_block { + // TODO: Produce a beacon block in a new thread + } + if work_type.attestation_duty.is_some() { + // available AttestationDuty info + let attestation_duty = + work_type.attestation_duty.expect("Cannot be None"); + let attester_grpc_client = + Arc::new( + AttestationGrpcClient::new( + service.attester_client.clone() + ) + ); + let signer = Arc::new(AttesterLocalSigner::new(keypair.clone())); + let attester = + Attester::new( + attester_grpc_client, + signer); + let mut attester_service = AttesterService { + attester, + poll_interval_millis: POLL_INTERVAL_MILLIS, + log: log.clone(), + }; + attester_service.run(); + } } } - } - Ok(()) - })) - .map_err(|e| format!("Service thread failed: {:?}", e))?; + Ok(()) + }) + .map_err(|e| format!("Service thread failed: {:?}", e)), + ); // completed a slot process Ok(()) From 867af4bc6ad5efcaeba3d6891031c2bcb3997a8c Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Thu, 28 Mar 2019 21:00:38 +1100 Subject: [PATCH 05/57] Made the 'signers' an Arc, so that things compile. --- validator_client/src/duties/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 9d92215928..0b86e3ce93 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -42,7 +42,7 @@ pub struct DutiesManager { pub duties_map: RwLock, /// A list of all signer objects known to the validator service. // TODO: Generalise the signers, so that they're not just keypairs - pub signers: Vec, + pub signers: Arc>, pub beacon_node: Arc, } @@ -97,7 +97,7 @@ impl DutiesManager { // if the map is poisoned, return None let duties = self.duties_map.read().ok()?; - for validator_signer in &self.signers { + for validator_signer in self.signers.iter() { match duties.is_work_slot(slot, &validator_signer) { Ok(Some(work_type)) => current_work.push((validator_signer.clone(), work_type)), Ok(None) => {} // No work for this validator From 87acaac8a03b2735b20e64887c7d5e0a5627f55a Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Thu, 28 Mar 2019 21:01:47 +1100 Subject: [PATCH 06/57] Ran cargo fmt. --- .../test_harness/src/validator_harness/mod.rs | 8 ++-- eth2/attester/src/lib.rs | 37 +++++++++++-------- .../src/test_utils/simulated_beacon_node.rs | 2 +- eth2/attester/src/traits.rs | 2 +- eth2/block_proposer/src/lib.rs | 18 ++------- .../attestation_grpc_client.rs | 2 +- validator_client/src/duties/epoch_duties.rs | 2 +- validator_client/src/duties/mod.rs | 2 +- validator_client/src/service.rs | 16 +++----- 9 files changed, 38 insertions(+), 51 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs b/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs index 074ede06a6..43ad03ea71 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs @@ -2,13 +2,13 @@ mod direct_beacon_node; mod direct_duties; mod local_signer; +use crate::direct_beacon_node::DirectBeaconNode; use attester::PollOutcome as AttestationPollOutcome; use attester::{Attester, Error as AttestationPollError}; use beacon_chain::BeaconChain; use block_proposer::PollOutcome as BlockPollOutcome; use block_proposer::{BlockProducer, Error as BlockPollError}; use db::MemoryDB; -use crate::direct_beacon_node::DirectBeaconNode; use fork_choice::BitwiseLMDGhost; use local_signer::LocalSigner; use slot_clock::TestingSlotClock; @@ -32,10 +32,8 @@ type TestingBlockProducer = BlockProducer< LocalSigner, >; -type TestingAttester = Attester< - DirectBeaconNode>, - LocalSigner, ->; +type TestingAttester = + Attester>, LocalSigner>; /// A `BlockProducer` and `Attester` which sign using a common keypair. /// diff --git a/eth2/attester/src/lib.rs b/eth2/attester/src/lib.rs index 53d81e8974..b5cdf5920b 100644 --- a/eth2/attester/src/lib.rs +++ b/eth2/attester/src/lib.rs @@ -3,8 +3,10 @@ mod traits; use ssz::TreeHash; use std::sync::Arc; -use types::{AttestationData, AttestationDataAndCustodyBit, Attestation, Signature, - AggregateSignature, Slot, AttestationDuty, Bitfield}; +use types::{ + AggregateSignature, Attestation, AttestationData, AttestationDataAndCustodyBit, + AttestationDuty, Bitfield, Signature, Slot, +}; pub use self::traits::{ BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer, @@ -59,20 +61,28 @@ impl Attester { } impl Attester { - - fn produce_attestation(&mut self, attestation_duty: AttestationDuty) -> Result { - let attestation_data = match self.beacon_node.produce_attestation_data( - attestation_duty.slot, - attestation_duty.shard - )? { + fn produce_attestation( + &mut self, + attestation_duty: AttestationDuty, + ) -> Result { + let attestation_data = match self + .beacon_node + .produce_attestation_data(attestation_duty.slot, attestation_duty.shard)? + { Some(attestation_data) => attestation_data, - None => return Ok(PollOutcome::BeaconNodeUnableToProduceAttestation(attestation_duty.slot)), + None => { + return Ok(PollOutcome::BeaconNodeUnableToProduceAttestation( + attestation_duty.slot, + )) + } }; dbg!(&attestation_data); if !self.safe_to_produce(&attestation_data) { - return Ok(PollOutcome::SlashableAttestationNotProduced(attestation_duty.slot)); + return Ok(PollOutcome::SlashableAttestationNotProduced( + attestation_duty.slot, + )); } let signature = match self.sign_attestation_data(&attestation_data) { @@ -82,7 +92,6 @@ impl Attester { let mut agg_sig = AggregateSignature::new(); agg_sig.add(&signature); - let attestation = Attestation { aggregation_bitfield: Bitfield::new(), data: attestation_data, @@ -172,10 +181,7 @@ mod tests { let attest_epoch = attest_slot / spec.slots_per_epoch; let attest_shard = 12; - let mut attester = Attester::new( - beacon_node.clone(), - signer.clone(), - ); + let mut attester = Attester::new(beacon_node.clone(), signer.clone()); // Configure responses from the BeaconNode. beacon_node.set_next_produce_result(Ok(Some(AttestationData::random_for_test(&mut rng)))); @@ -220,6 +226,5 @@ mod tests { Ok(PollOutcome::ProducerDutiesUnknown(slot)) ); */ - } } diff --git a/eth2/attester/src/test_utils/simulated_beacon_node.rs b/eth2/attester/src/test_utils/simulated_beacon_node.rs index 8e6c7c31d8..eed6a38b9e 100644 --- a/eth2/attester/src/test_utils/simulated_beacon_node.rs +++ b/eth2/attester/src/test_utils/simulated_beacon_node.rs @@ -1,6 +1,6 @@ use crate::traits::{BeaconNode, BeaconNodeError, PublishOutcome}; use std::sync::RwLock; -use types::{AttestationData, Attestation, Slot}; +use types::{Attestation, AttestationData, Slot}; type ProduceResult = Result, BeaconNodeError>; type PublishResult = Result; diff --git a/eth2/attester/src/traits.rs b/eth2/attester/src/traits.rs index 1c6d38578c..94d138ae07 100644 --- a/eth2/attester/src/traits.rs +++ b/eth2/attester/src/traits.rs @@ -1,4 +1,4 @@ -use types::{AttestationData, Attestation, Signature, Slot}; +use types::{Attestation, AttestationData, Signature, Slot}; #[derive(Debug, PartialEq, Clone)] pub enum BeaconNodeError { diff --git a/eth2/block_proposer/src/lib.rs b/eth2/block_proposer/src/lib.rs index 1d2a4af716..646391c383 100644 --- a/eth2/block_proposer/src/lib.rs +++ b/eth2/block_proposer/src/lib.rs @@ -4,7 +4,7 @@ mod traits; use slot_clock::SlotClock; use ssz::{SignedRoot, TreeHash}; use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, Domain, Slot, Fork}; +use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; pub use self::traits::{ BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer, @@ -57,11 +57,7 @@ pub struct BlockProducer { impl BlockProducer { /// Returns a new instance where `last_processed_slot == 0`. - pub fn new( - spec: Arc, - beacon_node: Arc, - signer: Arc, - ) -> Self { + pub fn new(spec: Arc, beacon_node: Arc, signer: Arc) -> Self { Self { last_processed_slot: None, spec, @@ -72,7 +68,6 @@ impl BlockProducer { } impl BlockProducer { - /* No longer needed because we don't poll any more /// "Poll" to see if the validator is required to take any action. /// @@ -129,7 +124,6 @@ impl BlockProducer { /// The slash-protection code is not yet implemented. There is zero protection against /// slashing. fn produce_block(&mut self, slot: Slot, fork: Fork) -> Result { - let randao_reveal = { // TODO: add domain, etc to this message. Also ensure result matches `into_to_bytes32`. let message = slot.epoch(self.spec.slots_per_epoch).hash_tree_root(); @@ -238,12 +232,8 @@ mod tests { let beacon_node = Arc::new(SimulatedBeaconNode::default()); let signer = Arc::new(LocalSigner::new(Keypair::random())); - - let mut block_proposer = BlockProducer::new( - spec.clone(), - beacon_node.clone(), - signer.clone(), - ); + let mut block_proposer = + BlockProducer::new(spec.clone(), beacon_node.clone(), signer.clone()); // Configure responses from the BeaconNode. beacon_node.set_next_produce_result(Ok(Some(BeaconBlock::random_for_test(&mut rng)))); diff --git a/validator_client/src/attester_service/attestation_grpc_client.rs b/validator_client/src/attester_service/attestation_grpc_client.rs index 97ec058992..05173d8248 100644 --- a/validator_client/src/attester_service/attestation_grpc_client.rs +++ b/validator_client/src/attester_service/attestation_grpc_client.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use attester::{BeaconNode, BeaconNodeError, PublishOutcome}; use protos::services::ProduceAttestationDataRequest; -use types::{AttestationData, Attestation, Slot}; +use types::{Attestation, AttestationData, Slot}; pub struct AttestationGrpcClient { client: Arc, diff --git a/validator_client/src/duties/epoch_duties.rs b/validator_client/src/duties/epoch_duties.rs index 984dc6e00d..47c9d96c49 100644 --- a/validator_client/src/duties/epoch_duties.rs +++ b/validator_client/src/duties/epoch_duties.rs @@ -44,7 +44,7 @@ impl EpochDuty { slot, shard: self.attestation_shard, committee_index: self.committee_index as usize, - validator_index: self.validator_index as usize + validator_index: self.validator_index as usize, }); } diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 0b86e3ce93..de08f6ce7f 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -102,7 +102,7 @@ impl DutiesManager { Ok(Some(work_type)) => current_work.push((validator_signer.clone(), work_type)), Ok(None) => {} // No work for this validator //TODO: This should really log an error, as we shouldn't end up with an err here. - Err(_) => {} // Unknown epoch or validator, no work + Err(_) => {} // Unknown epoch or validator, no work } } if current_work.is_empty() { diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 86f310a06d..36da36a1f1 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -20,6 +20,7 @@ use slog::{debug, error, info, warn}; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::sync::Arc; use std::sync::RwLock; +use std::thread; use std::time::{Duration, Instant, SystemTime}; use tokio::prelude::*; use tokio::runtime::Builder; @@ -27,7 +28,6 @@ use tokio::timer::Interval; use tokio_timer::clock::Clock; use types::test_utils::generate_deterministic_keypairs; use types::{Epoch, Fork, Slot}; -use std::thread; //TODO: This service should be simplified in the future. Can be made more steamlined. @@ -268,17 +268,11 @@ impl Service { // available AttestationDuty info let attestation_duty = work_type.attestation_duty.expect("Cannot be None"); - let attester_grpc_client = - Arc::new( - AttestationGrpcClient::new( - service.attester_client.clone() - ) - ); + let attester_grpc_client = Arc::new(AttestationGrpcClient::new( + service.attester_client.clone(), + )); let signer = Arc::new(AttesterLocalSigner::new(keypair.clone())); - let attester = - Attester::new( - attester_grpc_client, - signer); + let attester = Attester::new(attester_grpc_client, signer); let mut attester_service = AttesterService { attester, poll_interval_millis: POLL_INTERVAL_MILLIS, From 6c8abd8990e552dc847205edf5b3dc2f33c5fcc9 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Fri, 29 Mar 2019 00:02:41 +1100 Subject: [PATCH 07/57] Fixed merge conflict fail. --- validator_client/src/duties/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 569a99efa2..de08f6ce7f 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -51,11 +51,7 @@ impl DutiesManager { /// /// be a wall-clock (e.g., system time, remote server time, etc.). fn update(&self, epoch: Epoch) -> Result { -<<<<<<< HEAD let duties = self.beacon_node.request_duties(epoch, &self.signers)?; -======= - let duties = self.beacon_node.request_duties(epoch, &self.pubkeys)?; ->>>>>>> master { // If these duties were known, check to see if they're updates or identical. if let Some(known_duties) = self.duties_map.read()?.get(&epoch) { From 1e760d6719acc9bae01a8d2be7af21785c05aa9e Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 29 Mar 2019 00:43:53 +1100 Subject: [PATCH 08/57] Add Display for PublicKey --- eth2/utils/bls/src/public_key.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index 5a348f530d..5c4c3204c5 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -7,6 +7,7 @@ use ssz::{ decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash, }; use std::default; +use std::fmt; use std::hash::{Hash, Hasher}; /// A single BLS signature. @@ -54,6 +55,12 @@ impl PublicKey { } } +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.concatenated_hex_id()) + } +} + impl default::Default for PublicKey { fn default() -> Self { let secret_key = SecretKey::random(); From 405ea619e2de512013e7a02449660554a8b7b4ae Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 29 Mar 2019 00:45:39 +1100 Subject: [PATCH 09/57] Clean up validator output --- validator_client/src/service.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 83e7608550..614a66b611 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -235,7 +235,7 @@ impl Service { "The Timer should poll a new slot" ); - info!(log, "Processing slot: {}", current_slot.as_u64()); + info!(log, "Processing"; "slot" => current_slot.as_u64(), "epoch" => current_epoch.as_u64()); /* check for new duties */ @@ -243,8 +243,7 @@ impl Service { let cloned_log = log.clone(); // spawn a new thread separate to the runtime std::thread::spawn(move || { - cloned_manager.run_update(current_epoch.clone(), cloned_log.clone()); - dbg!("Finished thread"); + let _empty_error = cloned_manager.run_update(current_epoch.clone(), cloned_log.clone()); }); /* execute any specified duties */ From 68b33620c29b6d0f6f3dad591f1fdc301573e9a6 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 29 Mar 2019 02:23:03 +1100 Subject: [PATCH 10/57] Implement Display and clean validator output --- .../test_utils/testing_beacon_state_builder.rs | 2 +- validator_client/src/duties/epoch_duties.rs | 17 ++++++++++++++++- validator_client/src/duties/mod.rs | 14 ++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index b38e8b5273..5e4cebd576 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -120,7 +120,7 @@ impl TestingBeaconStateBuilder { }) .collect(); - let genesis_time = 1553753928; // arbitrary + let genesis_time = 1553776331; // arbitrary let mut state = BeaconState::genesis( genesis_time, diff --git a/validator_client/src/duties/epoch_duties.rs b/validator_client/src/duties/epoch_duties.rs index 5c23e82b1e..f177377894 100644 --- a/validator_client/src/duties/epoch_duties.rs +++ b/validator_client/src/duties/epoch_duties.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::fmt; use std::ops::{Deref, DerefMut}; use types::{AttestationDuty, Epoch, PublicKey, Slot}; @@ -55,11 +56,25 @@ impl EpochDuty { None } } + +impl fmt::Display for EpochDuty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut display_block = String::from("None"); + if let Some(block_slot) = self.block_production_slot { + display_block = block_slot.to_string(); + } + write!( + f, + "produce block slot: {}, attestation slot: {}, attestation shard: {}", + display_block, self.attestation_slot, self.attestation_shard + ) + } +} + /// Maps a list of public keys (many validators) to an EpochDuty. pub type EpochDuties = HashMap>; pub enum EpochDutiesMapError { - Poisoned, UnknownEpoch, UnknownValidator, } diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 0e962053e1..9fce1a3536 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -82,7 +82,8 @@ impl DutiesManager { info!(log, "Duties changed (potential re-org)"; "epoch" => epoch, "duties" => format!("{:?}", duties)) } Ok(UpdateOutcome::NewDuties(epoch, duties)) => { - info!(log, "New duties obtained"; "epoch" => epoch, "duties" => format!("{:?}", duties)) + info!(log, "New duties obtained"; "epoch" => epoch); + print_duties(&log, duties); } }; Ok(Async::Ready(())) @@ -126,13 +127,22 @@ impl From> for Error { impl From for Error { fn from(e: EpochDutiesMapError) -> Error { match e { - EpochDutiesMapError::Poisoned => Error::EpochMapPoisoned, EpochDutiesMapError::UnknownEpoch => Error::UnknownEpoch, EpochDutiesMapError::UnknownValidator => Error::UnknownValidator, } } } +fn print_duties(log: &slog::Logger, duties: EpochDuties) { + for (pk, duty) in duties.iter() { + if let Some(display_duty) = duty { + info!(log, "Validator: {}",pk; "Duty" => format!("{}",display_duty)); + } else { + info!(log, "Validator: {}",pk; "Duty" => "None"); + } + } +} + /* TODO: Modify tests for new Duties Manager form #[cfg(test)] mod tests { From be592c86d11fcd6cc0b80e0939d657355ac512b3 Mon Sep 17 00:00:00 2001 From: Luke Anderson Date: Fri, 29 Mar 2019 10:39:37 +1100 Subject: [PATCH 11/57] Started migrating FreeAttestation to Attestation in the harnesses - doesn't compile yet. --- .../test_harness/src/beacon_chain_harness.rs | 12 ++++++------ .../src/validator_harness/direct_beacon_node.rs | 14 +++++++------- .../test_harness/src/validator_harness/mod.rs | 14 ++------------ 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 9424974763..9f6643c7d3 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -137,11 +137,11 @@ impl BeaconChainHarness { slot } - /// Gather the `FreeAttestation`s from the valiators. + /// Gather the `Attestation`s from the valiators. /// /// Note: validators will only produce attestations _once per slot_. So, if you call this twice /// you'll only get attestations on the first run. - pub fn gather_free_attesations(&mut self) -> Vec { + pub fn gather_attesations(&mut self) -> Vec { let present_slot = self.beacon_chain.present_slot(); let attesting_validators = self @@ -158,7 +158,7 @@ impl BeaconChainHarness { let attesting_validators: HashSet = HashSet::from_iter(attesting_validators.iter().cloned()); - let free_attestations: Vec = self + let attestations: Vec = self .validators .par_iter_mut() .enumerate() @@ -176,8 +176,8 @@ impl BeaconChainHarness { .collect(); debug!( - "Gathered {} FreeAttestations for slot {}.", - free_attestations.len(), + "Gathered {} Attestations for slot {}.", + attestations.len(), present_slot ); @@ -232,7 +232,7 @@ impl BeaconChainHarness { .unwrap(); }); - debug!("Free attestations processed."); + debug!("attestations processed."); block } 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 17630833bb..7fc0376c8b 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 @@ -12,7 +12,7 @@ use fork_choice::ForkChoice; use parking_lot::RwLock; use slot_clock::SlotClock; use std::sync::Arc; -use types::{AttestationData, BeaconBlock, FreeAttestation, Signature, Slot}; +use types::{AttestationData, BeaconBlock, Attestation, Signature, Slot}; // mod attester; // mod producer; @@ -20,13 +20,13 @@ use types::{AttestationData, BeaconBlock, FreeAttestation, Signature, Slot}; /// Connect directly to a borrowed `BeaconChain` instance so an attester/producer can request/submit /// blocks/attestations. /// -/// `BeaconBlock`s and `FreeAttestation`s are not actually published to the `BeaconChain`, instead +/// `BeaconBlock`s and `Attestation`s are not actually published to the `BeaconChain`, instead /// they are stored inside this struct. This is to allow one to benchmark the submission of the /// block/attestation directly, or modify it before submission. pub struct DirectBeaconNode { beacon_chain: Arc>, published_blocks: RwLock>, - published_attestations: RwLock>, + published_attestations: RwLock>, } impl DirectBeaconNode { @@ -44,7 +44,7 @@ impl DirectBeaconNode { } /// Get the last published attestation (if any). - pub fn last_published_free_attestation(&self) -> Option { + pub fn last_published_free_attestation(&self) -> Option { Some(self.published_attestations.read().last()?.clone()) } } @@ -55,7 +55,7 @@ impl AttesterBeaconNode for DirectBeac _slot: Slot, shard: u64, ) -> Result, NodeError> { - match self.beacon_chain.produce_attestation(shard) { + match self.beacon_chain.produce_attestation_data(shard) { Ok(attestation_data) => Ok(Some(attestation_data)), Err(e) => Err(NodeError::RemoteFailure(format!("{:?}", e))), } @@ -63,9 +63,9 @@ impl AttesterBeaconNode for DirectBeac fn publish_attestation( &self, - free_attestation: FreeAttestation, + attestation: Attestation, ) -> Result { - self.published_attestations.write().push(free_attestation); + self.published_attestations.write().push(attestation); Ok(AttestationPublishOutcome::ValidAttestation) } } diff --git a/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs b/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs index 43ad03ea71..60258c794c 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs @@ -2,7 +2,7 @@ mod direct_beacon_node; mod direct_duties; mod local_signer; -use crate::direct_beacon_node::DirectBeaconNode; +use crate::validator_harness::direct_beacon_node::DirectBeaconNode; use attester::PollOutcome as AttestationPollOutcome; use attester::{Attester, Error as AttestationPollError}; use beacon_chain::BeaconChain; @@ -44,10 +44,8 @@ pub struct ValidatorHarness { pub block_producer: TestingBlockProducer, pub attester: TestingAttester, pub spec: Arc, - pub epoch_map: Arc>>, pub keypair: Keypair, pub beacon_node: Arc>>, - pub slot_clock: Arc, pub signer: Arc, } @@ -61,22 +59,16 @@ impl ValidatorHarness { beacon_chain: Arc>>, spec: Arc, ) -> Self { - let slot_clock = Arc::new(TestingSlotClock::new(spec.genesis_slot.as_u64())); let signer = Arc::new(LocalSigner::new(keypair.clone())); let beacon_node = Arc::new(DirectBeaconNode::new(beacon_chain.clone())); - let epoch_map = Arc::new(DirectDuties::new(keypair.pk.clone(), beacon_chain.clone())); let block_producer = BlockProducer::new( spec.clone(), - epoch_map.clone(), - slot_clock.clone(), beacon_node.clone(), signer.clone(), ); let attester = Attester::new( - epoch_map.clone(), - slot_clock.clone(), beacon_node.clone(), signer.clone(), ); @@ -85,10 +77,8 @@ impl ValidatorHarness { block_producer, attester, spec, - epoch_map, keypair, beacon_node, - slot_clock, signer, } } @@ -113,7 +103,7 @@ impl ValidatorHarness { /// Run the `poll` function on the `Attester` and produce a `FreeAttestation`. /// /// An error is returned if the attester refuses to attest. - pub fn produce_free_attestation(&mut self) -> Result { + pub fn produce_attestation(&mut self) -> Result { match self.attester.poll() { Ok(AttestationPollOutcome::AttestationProduced(_)) => {} Ok(outcome) => return Err(AttestationProduceError::DidNotProduce(outcome)), From aa29a66facc79db11b3d45f827b5010ad363b5ac Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 29 Mar 2019 10:49:43 +1100 Subject: [PATCH 12/57] Add node chain-id validation for validation client --- validator_client/src/service.rs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 614a66b611..fb0304f87a 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -1,4 +1,13 @@ -/// The validator service. Connects to a beacon node and signs blocks when required. +/// The Validator Client service. +/// +/// Connects to a beacon node and negotiates the correct chain id. +/// +/// Once connected, the service loads known validators keypairs from disk. Every slot, +/// the service pings the beacon node, asking for new duties for each of the validators. +/// +/// When a validator needs to either produce a block or sign an attestation, it requests the +/// data from the beacon node and performs the signing before publishing the block to the beacon +/// node. use crate::attester_service::{AttestationGrpcClient, AttesterService}; use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use crate::config::Config as ValidatorConfig; @@ -36,8 +45,6 @@ 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 for this service. slot_clock: SystemTimeSlotClock, @@ -74,7 +81,7 @@ impl Service { Arc::new(BeaconNodeServiceClient::new(ch)) }; - // retrieve node information + // retrieve node information and validate the beacon node let node_info = loop { match beacon_node_client.info(&Empty::new()) { Err(e) => { @@ -84,18 +91,27 @@ impl Service { continue; } Ok(info) => { + // verify the node's genesis time if SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs() < info.genesis_time { - warn!( + error!( log, "Beacon Node's genesis time is in the future. No work to do.\n Exiting" ); return Err("Genesis time in the future".into()); } + // verify the node's chain id + if config.spec.chain_id != info.chain_id as u8 { + error!( + log, + "Beacon Node's genesis time is in the future. No work to do.\n Exiting" + ); + return Err(format!("Beacon node has the wrong chain id. Expected chain id: {}, node's chain id: {}", config.spec.chain_id, info.chain_id).into()); + } break info; } }; @@ -150,7 +166,6 @@ impl Service { Ok(Self { connected_node_version: node_info.version, - chain_id: node_info.chain_id as u16, fork, slot_clock, current_slot, From f8201edddda0e5bbace08dd205e1bd5145377d76 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 29 Mar 2019 14:52:08 +1100 Subject: [PATCH 13/57] Initial layout of beacon block production --- validator_client/src/beacon_block_node.rs | 23 ++ .../beacon_block_grpc_client.rs | 81 ------- .../block_producer_service/block_producer.rs | 227 ++++++++++++++++++ .../src/block_producer_service/mod.rs | 125 ++++++---- validator_client/src/service.rs | 3 +- 5 files changed, 323 insertions(+), 136 deletions(-) create mode 100644 validator_client/src/beacon_block_node.rs delete mode 100644 validator_client/src/block_producer_service/beacon_block_grpc_client.rs create mode 100644 validator_client/src/block_producer_service/block_producer.rs diff --git a/validator_client/src/beacon_block_node.rs b/validator_client/src/beacon_block_node.rs new file mode 100644 index 0000000000..bc85ef194a --- /dev/null +++ b/validator_client/src/beacon_block_node.rs @@ -0,0 +1,23 @@ +#[Derive(Debug, PartialEq, Clone)] +pub enum BeaconNodeError { + RemoteFailure(String), + DecodeFailure, +} + +/// Defines the methods required to produce and publish blocks on a Beacon Node. Abstracts the +/// actual beacon node. +pub trait BeaconNode: Send + Sync { + /// Request that the node produces a block. + /// + /// Returns Ok(None) if the Beacon Node is unable to produce at the given slot. + fn produce_beacon_block( + &self, + slot: Slot, + randao_reveal: &Signature, + ) -> Result, BeaconNodeError>; + + /// Request that the node publishes a block. + /// + /// Returns `true` if the publish was sucessful. + fn publish_beacon_block(&self, block: BeaconBlock) -> Result; +} diff --git a/validator_client/src/block_producer_service/beacon_block_grpc_client.rs b/validator_client/src/block_producer_service/beacon_block_grpc_client.rs deleted file mode 100644 index ba2acfffbb..0000000000 --- a/validator_client/src/block_producer_service/beacon_block_grpc_client.rs +++ /dev/null @@ -1,81 +0,0 @@ -use block_proposer::{BeaconNode, BeaconNodeError, PublishOutcome}; -use protos::services::{ - BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, -}; -use protos::services_grpc::BeaconBlockServiceClient; -use ssz::{ssz_encode, Decodable}; -use std::sync::Arc; -use types::{BeaconBlock, Signature, Slot}; - -/// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be -/// implemented upon it. -pub struct BeaconBlockGrpcClient { - client: Arc, -} - -impl BeaconBlockGrpcClient { - pub fn new(client: Arc) -> Self { - Self { client } - } -} - -impl BeaconNode for BeaconBlockGrpcClient { - /// Request a Beacon Node (BN) to produce a new block at the supplied slot. - /// - /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the - /// BN is unable to find a parent block. - fn produce_beacon_block( - &self, - slot: Slot, - // TODO: use randao_reveal, when proto APIs have been updated. - _randao_reveal: &Signature, - ) -> Result, BeaconNodeError> { - let mut req = ProduceBeaconBlockRequest::new(); - req.set_slot(slot.as_u64()); - - let reply = self - .client - .produce_beacon_block(&req) - .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; - - if reply.has_block() { - let block = reply.get_block(); - let ssz = block.get_ssz(); - - let (block, _i) = - BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| BeaconNodeError::DecodeFailure)?; - - Ok(Some(block)) - } else { - Ok(None) - } - } - - /// Request a Beacon Node (BN) to publish a block. - /// - /// Generally, this will be called after a `produce_beacon_block` call with a block that has - /// been completed (signed) by the validator client. - fn publish_beacon_block(&self, block: BeaconBlock) -> Result { - let mut req = PublishBeaconBlockRequest::new(); - - let ssz = ssz_encode(&block); - - // TODO: this conversion is incomplete; fix it. - let mut grpc_block = GrpcBeaconBlock::new(); - grpc_block.set_ssz(ssz); - - req.set_block(grpc_block); - - let reply = self - .client - .publish_beacon_block(&req) - .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; - - if reply.get_success() { - Ok(PublishOutcome::ValidBlock) - } else { - // TODO: distinguish between different errors - Ok(PublishOutcome::InvalidBlock("Publish failed".to_string())) - } - } -} diff --git a/validator_client/src/block_producer_service/block_producer.rs b/validator_client/src/block_producer_service/block_producer.rs new file mode 100644 index 0000000000..187f2b6499 --- /dev/null +++ b/validator_client/src/block_producer_service/block_producer.rs @@ -0,0 +1,227 @@ +pub mod test_utils; +mod traits; + +use slot_clock::SlotClock; +use ssz::{SignedRoot, TreeHash}; +use std::sync::Arc; +use types::{BeaconBlock, ChainSpec, Domain, Slot}; + +#[derive(Debug, PartialEq)] +pub enum Error { + SlotClockError, + SlotUnknowable, + EpochMapPoisoned, + SlotClockPoisoned, + EpochLengthIsZero, + BeaconNodeError(BeaconNodeError), +} + +#[derive(Debug, PartialEq)] +pub enum BlockProducerEvent { + /// A new block was produced. + BlockProduced(Slot), + /// A block was not produced as it would have been slashable. + SlashableBlockNotProduced(Slot), + /// The Beacon Node was unable to produce a block at that slot. + BeaconNodeUnableToProduceBlock(Slot), + /// The signer failed to sign the message. + SignerRejection(Slot), + /// The public key for this validator is not an active validator. + ValidatorIsUnknown(Slot), +} + +/// This struct contains the logic for requesting and signing beacon blocks for a validator. The +/// validator can abstractly sign via the Signer trait object. +pub struct BlockProducer { + /// The current fork. + pub fork: Fork, + /// The current slot to produce a block for. + pub slot: Slot, + /// The current epoch. + pub epoch: Epoch, + /// The beacon node to connect to. + pub beacon_node: Arc, + /// The signer to sign the block. + pub signer: Arc, +} + +impl BlockProducer { + + /// Produce a block at some slot. + /// + /// Assumes that a block is required at this slot (does not check the duties). + /// + /// Ensures the message is not slashable. + /// + /// !!! UNSAFE !!! + /// + /// The slash-protection code is not yet implemented. There is zero protection against + /// slashing. + fn produce_block(&mut self) -> Result { + + let randao_reveal = { + // TODO: add domain, etc to this message. Also ensure result matches `into_to_bytes32`. + let message = slot.epoch(self.spec.slots_per_epoch).hash_tree_root(); + + match self.signer.sign_randao_reveal( + &message, + self.spec + .get_domain(slot.epoch(self.spec.slots_per_epoch), Domain::Randao, &fork), + ) { + None => return Ok(PollOutcome::SignerRejection(slot)), + Some(signature) => signature, + } + }; + + if let Some(block) = self + .beacon_node + .produce_beacon_block(slot, &randao_reveal)? + { + if self.safe_to_produce(&block) { + let domain = self.spec.get_domain( + slot.epoch(self.spec.slots_per_epoch), + Domain::BeaconBlock, + &fork, + ); + if let Some(block) = self.sign_block(block, domain) { + self.beacon_node.publish_beacon_block(block)?; + Ok(PollOutcome::BlockProduced(slot)) + } else { + Ok(PollOutcome::SignerRejection(slot)) + } + } else { + Ok(PollOutcome::SlashableBlockNotProduced(slot)) + } + } else { + Ok(PollOutcome::BeaconNodeUnableToProduceBlock(slot)) + } + } + + /// Consumes a block, returning that block signed by the validators private key. + /// + /// Important: this function will not check to ensure the block is not slashable. This must be + /// done upstream. + fn sign_block(&mut self, mut block: BeaconBlock, domain: u64) -> Option { + self.store_produce(&block); + + match self + .signer + .sign_block_proposal(&block.signed_root()[..], domain) + { + None => None, + Some(signature) => { + block.signature = signature; + Some(block) + } + } + } + + /// Returns `true` if signing a block is safe (non-slashable). + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn safe_to_produce(&self, _block: &BeaconBlock) -> bool { + // TODO: ensure the producer doesn't produce slashable blocks. + // https://github.com/sigp/lighthouse/issues/160 + true + } + + /// Record that a block was produced so that slashable votes may not be made in the future. + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn store_produce(&mut self, _block: &BeaconBlock) { + // TODO: record this block production to prevent future slashings. + // https://github.com/sigp/lighthouse/issues/160 + } +} + +impl From for Error { + fn from(e: BeaconNodeError) -> Error { + Error::BeaconNodeError(e) + } +} + + +/* Old tests - Re-work for new logic +#[cfg(test)] +mod tests { + use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode}; + use super::*; + use slot_clock::TestingSlotClock; + use types::{ + test_utils::{SeedableRng, TestRandom, XorShiftRng}, + Keypair, + }; + + // TODO: implement more thorough testing. + // https://github.com/sigp/lighthouse/issues/160 + // + // These tests should serve as a good example for future tests. + + #[test] + pub fn polling() { + let mut rng = XorShiftRng::from_seed([42; 16]); + + let spec = Arc::new(ChainSpec::foundation()); + let slot_clock = Arc::new(TestingSlotClock::new(0)); + let beacon_node = Arc::new(SimulatedBeaconNode::default()); + let signer = Arc::new(LocalSigner::new(Keypair::random())); + + let mut epoch_map = EpochMap::new(spec.slots_per_epoch); + let produce_slot = Slot::new(100); + let produce_epoch = produce_slot.epoch(spec.slots_per_epoch); + epoch_map.map.insert(produce_epoch, produce_slot); + let epoch_map = Arc::new(epoch_map); + + let mut block_proposer = BlockProducer::new( + spec.clone(), + epoch_map.clone(), + slot_clock.clone(), + beacon_node.clone(), + signer.clone(), + ); + + // Configure responses from the BeaconNode. + beacon_node.set_next_produce_result(Ok(Some(BeaconBlock::random_for_test(&mut rng)))); + beacon_node.set_next_publish_result(Ok(PublishOutcome::ValidBlock)); + + // One slot before production slot... + slot_clock.set_slot(produce_slot.as_u64() - 1); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProductionNotRequired(produce_slot - 1)) + ); + + // On the produce slot... + slot_clock.set_slot(produce_slot.as_u64()); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProduced(produce_slot.into())) + ); + + // Trying the same produce slot again... + slot_clock.set_slot(produce_slot.as_u64()); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::SlotAlreadyProcessed(produce_slot)) + ); + + // One slot after the produce slot... + slot_clock.set_slot(produce_slot.as_u64() + 1); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProductionNotRequired(produce_slot + 1)) + ); + + // In an epoch without known duties... + let slot = (produce_epoch.as_u64() + 1) * spec.slots_per_epoch; + slot_clock.set_slot(slot); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::ProducerDutiesUnknown(Slot::new(slot))) + ); + } +} diff --git a/validator_client/src/block_producer_service/mod.rs b/validator_client/src/block_producer_service/mod.rs index 91e7606a7f..8a8cce6134 100644 --- a/validator_client/src/block_producer_service/mod.rs +++ b/validator_client/src/block_producer_service/mod.rs @@ -1,61 +1,80 @@ -mod beacon_block_grpc_client; -// mod block_producer_service; - -use block_proposer::{ - BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, Signer, +use protos::services::{ + BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, }; -use slog::{error, info, warn, Logger}; -use slot_clock::SlotClock; -use std::time::Duration; +use protos::services_grpc::BeaconBlockServiceClient; +use ssz::{ssz_encode, Decodable}; +use std::sync::Arc; +use types::{BeaconBlock, Signature, Slot}; -pub use self::beacon_block_grpc_client::BeaconBlockGrpcClient; - -pub struct BlockProducerService { - pub block_producer: BlockProducer, - pub poll_interval_millis: u64, - pub log: Logger, +/// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be +/// implemented upon it. +pub struct BeaconBlockGrpcClient { + inner: Arc, } -impl BlockProducerService { - /// Run a loop which polls the block producer each `poll_interval_millis` millseconds. - /// - /// Logs the results of the polls. - pub fn run(&mut self) { - loop { - match self.block_producer.poll() { - Err(error) => { - error!(self.log, "Block producer poll error"; "error" => format!("{:?}", error)) - } - Ok(BlockProducerPollOutcome::BlockProduced(slot)) => { - info!(self.log, "Produced block"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::SlashableBlockNotProduced(slot)) => { - warn!(self.log, "Slashable block was not signed"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::BlockProductionNotRequired(slot)) => { - info!(self.log, "Block production not required"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::ProducerDutiesUnknown(slot)) => { - error!(self.log, "Block production duties unknown"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::SlotAlreadyProcessed(slot)) => { - warn!(self.log, "Attempted to re-process slot"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::BeaconNodeUnableToProduceBlock(slot)) => { - error!(self.log, "Beacon node unable to produce block"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::SignerRejection(slot)) => { - error!(self.log, "The cryptographic signer refused to sign the block"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::ValidatorIsUnknown(slot)) => { - error!(self.log, "The Beacon Node does not recognise the validator"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::UnableToGetFork(slot)) => { - error!(self.log, "Unable to get a `Fork` struct to generate signature domains"; "slot" => slot) - } - }; +impl BeaconBlockGrpcClient { + pub fn new(client: Arc) -> Self { + Self { inner: client } + } +} - std::thread::sleep(Duration::from_millis(self.poll_interval_millis)); +impl BeaconNode for BeaconBlockGrpcClient { + /// Request a Beacon Node (BN) to produce a new block at the supplied slot. + /// + /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the + /// BN is unable to find a parent block. + fn produce_beacon_block( + &self, + slot: Slot, + // TODO: use randao_reveal, when proto APIs have been updated. + _randao_reveal: &Signature, + ) -> Result, BeaconNodeError> { + let mut req = ProduceBeaconBlockRequest::new(); + req.set_slot(slot.as_u64()); + + let reply = self + .client + .produce_beacon_block(&req) + .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + + if reply.has_block() { + let block = reply.get_block(); + let ssz = block.get_ssz(); + + let (block, _i) = + BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| BeaconNodeError::DecodeFailure)?; + + Ok(Some(block)) + } else { + Ok(None) + } + } + + /// Request a Beacon Node (BN) to publish a block. + /// + /// Generally, this will be called after a `produce_beacon_block` call with a block that has + /// been completed (signed) by the validator client. + fn publish_beacon_block(&self, block: BeaconBlock) -> Result { + let mut req = PublishBeaconBlockRequest::new(); + + let ssz = ssz_encode(&block); + + // TODO: this conversion is incomplete; fix it. + let mut grpc_block = GrpcBeaconBlock::new(); + grpc_block.set_ssz(ssz); + + req.set_block(grpc_block); + + let reply = self + .client + .publish_beacon_block(&req) + .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + + if reply.get_success() { + Ok(PublishOutcome::ValidBlock) + } else { + // TODO: distinguish between different errors + Ok(PublishOutcome::InvalidBlock("Publish failed".to_string())) } } } diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index fb0304f87a..64ed7cb03b 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -78,7 +78,7 @@ impl Service { // Beacon node gRPC beacon node endpoints. let beacon_node_client = { let ch = ChannelBuilder::new(env.clone()).connect(&config.server); - Arc::new(BeaconNodeServiceClient::new(ch)) + BeaconNodeServiceClient::new(ch) }; // retrieve node information and validate the beacon node @@ -287,7 +287,6 @@ impl Service { } /* - // Spawn a new thread to perform block production for the validator. let producer_thread = { let spec = spec.clone(); From eea772de3eb059530460fb845632312bc6d6f358 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 29 Mar 2019 16:33:27 +1100 Subject: [PATCH 14/57] Implement block producer for validator client --- protos/src/services.proto | 3 + validator_client/Cargo.toml | 5 -- validator_client/src/beacon_block_node.rs | 23 ------- .../beacon_block_node.rs | 33 ++++++++++ .../block_producer_service/block_producer.rs | 60 ++++++++----------- .../src/block_producer_service/mod.rs | 33 ++++++---- validator_client/src/lib.rs | 3 - validator_client/src/main.rs | 1 + validator_client/src/service.rs | 2 +- validator_client/src/signer.rs | 7 +++ 10 files changed, 92 insertions(+), 78 deletions(-) delete mode 100644 validator_client/src/beacon_block_node.rs create mode 100644 validator_client/src/block_producer_service/beacon_block_node.rs delete mode 100644 validator_client/src/lib.rs create mode 100644 validator_client/src/signer.rs diff --git a/protos/src/services.proto b/protos/src/services.proto index dd82855a19..e5095f386b 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -19,7 +19,9 @@ service BeaconNodeService { /// Service that handles block production service BeaconBlockService { + // Requests a block to be signed from the beacon node. rpc ProduceBeaconBlock(ProduceBeaconBlockRequest) returns (ProduceBeaconBlockResponse); + // Responds to the node the signed block to be published. rpc PublishBeaconBlock(PublishBeaconBlockRequest) returns (PublishBeaconBlockResponse); } @@ -64,6 +66,7 @@ message Empty {} // Validator requests an unsigned proposal. message ProduceBeaconBlockRequest { uint64 slot = 1; + bytes randao_reveal = 2; } // Beacon node returns an unsigned proposal. diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 570e06d74c..209ebf25e7 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -8,11 +8,6 @@ edition = "2018" name = "validator_client" path = "src/main.rs" -[lib] -name = "validator_client" -path = "src/lib.rs" - - [dependencies] block_proposer = { path = "../eth2/block_proposer" } attester = { path = "../eth2/attester" } diff --git a/validator_client/src/beacon_block_node.rs b/validator_client/src/beacon_block_node.rs deleted file mode 100644 index bc85ef194a..0000000000 --- a/validator_client/src/beacon_block_node.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[Derive(Debug, PartialEq, Clone)] -pub enum BeaconNodeError { - RemoteFailure(String), - DecodeFailure, -} - -/// Defines the methods required to produce and publish blocks on a Beacon Node. Abstracts the -/// actual beacon node. -pub trait BeaconNode: Send + Sync { - /// Request that the node produces a block. - /// - /// Returns Ok(None) if the Beacon Node is unable to produce at the given slot. - fn produce_beacon_block( - &self, - slot: Slot, - randao_reveal: &Signature, - ) -> Result, BeaconNodeError>; - - /// Request that the node publishes a block. - /// - /// Returns `true` if the publish was sucessful. - fn publish_beacon_block(&self, block: BeaconBlock) -> Result; -} diff --git a/validator_client/src/block_producer_service/beacon_block_node.rs b/validator_client/src/block_producer_service/beacon_block_node.rs new file mode 100644 index 0000000000..5a581a4a7b --- /dev/null +++ b/validator_client/src/block_producer_service/beacon_block_node.rs @@ -0,0 +1,33 @@ +use types::{BeaconBlock, Signature, Slot}; +#[derive(Debug, PartialEq, Clone)] +pub enum BeaconBlockNodeError { + RemoteFailure(String), + DecodeFailure, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum PublishOutcome { + ValidBlock, + InvalidBlock(String), +} + +/// Defines the methods required to produce and publish blocks on a Beacon Node. Abstracts the +/// actual beacon node. +pub trait BeaconBlockNode: Send + Sync { + /// Request that the node produces a block. + /// + /// Returns Ok(None) if the Beacon Node is unable to produce at the given slot. + fn produce_beacon_block( + &self, + slot: Slot, + randao_reveal: &Signature, + ) -> Result, BeaconBlockNodeError>; + + /// Request that the node publishes a block. + /// + /// Returns `true` if the publish was successful. + fn publish_beacon_block( + &self, + block: BeaconBlock, + ) -> Result; +} diff --git a/validator_client/src/block_producer_service/block_producer.rs b/validator_client/src/block_producer_service/block_producer.rs index 187f2b6499..e71e6cd4bb 100644 --- a/validator_client/src/block_producer_service/block_producer.rs +++ b/validator_client/src/block_producer_service/block_producer.rs @@ -1,10 +1,8 @@ -pub mod test_utils; -mod traits; - -use slot_clock::SlotClock; +use super::beacon_block_node::{BeaconBlockNode, BeaconBlockNodeError}; +use crate::signer::Signer; use ssz::{SignedRoot, TreeHash}; use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, Domain, Slot}; +use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; #[derive(Debug, PartialEq)] pub enum Error { @@ -13,11 +11,11 @@ pub enum Error { EpochMapPoisoned, SlotClockPoisoned, EpochLengthIsZero, - BeaconNodeError(BeaconNodeError), + BeaconBlockNodeError(BeaconBlockNodeError), } #[derive(Debug, PartialEq)] -pub enum BlockProducerEvent { +pub enum ValidatorEvent { /// A new block was produced. BlockProduced(Slot), /// A block was not produced as it would have been slashable. @@ -32,21 +30,20 @@ pub enum BlockProducerEvent { /// This struct contains the logic for requesting and signing beacon blocks for a validator. The /// validator can abstractly sign via the Signer trait object. -pub struct BlockProducer { +pub struct BlockProducer { /// The current fork. pub fork: Fork, /// The current slot to produce a block for. pub slot: Slot, /// The current epoch. - pub epoch: Epoch, + pub spec: Arc, /// The beacon node to connect to. pub beacon_node: Arc, /// The signer to sign the block. pub signer: Arc, } -impl BlockProducer { - +impl BlockProducer { /// Produce a block at some slot. /// /// Assumes that a block is required at this slot (does not check the duties). @@ -57,43 +54,38 @@ impl BlockProducer { /// /// The slash-protection code is not yet implemented. There is zero protection against /// slashing. - fn produce_block(&mut self) -> Result { + fn produce_block(&mut self) -> Result { + let epoch = self.slot.epoch(self.spec.slots_per_epoch); let randao_reveal = { - // TODO: add domain, etc to this message. Also ensure result matches `into_to_bytes32`. - let message = slot.epoch(self.spec.slots_per_epoch).hash_tree_root(); - - match self.signer.sign_randao_reveal( + let message = epoch.hash_tree_root(); + let randao_reveal = match self.signer.sign_randao_reveal( &message, - self.spec - .get_domain(slot.epoch(self.spec.slots_per_epoch), Domain::Randao, &fork), + self.spec.get_domain(epoch, Domain::Randao, &self.fork), ) { - None => return Ok(PollOutcome::SignerRejection(slot)), + None => return Ok(ValidatorEvent::SignerRejection(self.slot)), Some(signature) => signature, - } + }; + randao_reveal }; if let Some(block) = self .beacon_node - .produce_beacon_block(slot, &randao_reveal)? + .produce_beacon_block(self.slot, &randao_reveal)? { if self.safe_to_produce(&block) { - let domain = self.spec.get_domain( - slot.epoch(self.spec.slots_per_epoch), - Domain::BeaconBlock, - &fork, - ); + let domain = self.spec.get_domain(epoch, Domain::BeaconBlock, &self.fork); if let Some(block) = self.sign_block(block, domain) { self.beacon_node.publish_beacon_block(block)?; - Ok(PollOutcome::BlockProduced(slot)) + Ok(ValidatorEvent::BlockProduced(self.slot)) } else { - Ok(PollOutcome::SignerRejection(slot)) + Ok(ValidatorEvent::SignerRejection(self.slot)) } } else { - Ok(PollOutcome::SlashableBlockNotProduced(slot)) + Ok(ValidatorEvent::SlashableBlockNotProduced(self.slot)) } } else { - Ok(PollOutcome::BeaconNodeUnableToProduceBlock(slot)) + Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(self.slot)) } } @@ -138,13 +130,12 @@ impl BlockProducer { } } -impl From for Error { - fn from(e: BeaconNodeError) -> Error { - Error::BeaconNodeError(e) +impl From for Error { + fn from(e: BeaconBlockNodeError) -> Error { + Error::BeaconBlockNodeError(e) } } - /* Old tests - Re-work for new logic #[cfg(test)] mod tests { @@ -225,3 +216,4 @@ mod tests { ); } } +*/ diff --git a/validator_client/src/block_producer_service/mod.rs b/validator_client/src/block_producer_service/mod.rs index 8a8cce6134..fe34f627d8 100644 --- a/validator_client/src/block_producer_service/mod.rs +++ b/validator_client/src/block_producer_service/mod.rs @@ -1,3 +1,7 @@ +mod beacon_block_node; +mod block_producer; + +use self::beacon_block_node::*; use protos::services::{ BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, }; @@ -9,16 +13,16 @@ use types::{BeaconBlock, Signature, Slot}; /// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be /// implemented upon it. pub struct BeaconBlockGrpcClient { - inner: Arc, + client: Arc, } impl BeaconBlockGrpcClient { pub fn new(client: Arc) -> Self { - Self { inner: client } + Self { client } } } -impl BeaconNode for BeaconBlockGrpcClient { +impl BeaconBlockNode for BeaconBlockGrpcClient { /// Request a Beacon Node (BN) to produce a new block at the supplied slot. /// /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the @@ -26,23 +30,26 @@ impl BeaconNode for BeaconBlockGrpcClient { fn produce_beacon_block( &self, slot: Slot, - // TODO: use randao_reveal, when proto APIs have been updated. - _randao_reveal: &Signature, - ) -> Result, BeaconNodeError> { + randao_reveal: &Signature, + ) -> Result, BeaconBlockNodeError> { + // request a beacon block from the node let mut req = ProduceBeaconBlockRequest::new(); req.set_slot(slot.as_u64()); + req.set_randao_reveal(ssz_encode(randao_reveal)); + //TODO: Determine if we want an explicit timeout let reply = self .client .produce_beacon_block(&req) - .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + // format the reply if reply.has_block() { let block = reply.get_block(); let ssz = block.get_ssz(); - let (block, _i) = - BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| BeaconNodeError::DecodeFailure)?; + let (block, _i) = BeaconBlock::ssz_decode(&ssz, 0) + .map_err(|_| BeaconBlockNodeError::DecodeFailure)?; Ok(Some(block)) } else { @@ -54,12 +61,14 @@ impl BeaconNode for BeaconBlockGrpcClient { /// /// Generally, this will be called after a `produce_beacon_block` call with a block that has /// been completed (signed) by the validator client. - fn publish_beacon_block(&self, block: BeaconBlock) -> Result { + fn publish_beacon_block( + &self, + block: BeaconBlock, + ) -> Result { let mut req = PublishBeaconBlockRequest::new(); let ssz = ssz_encode(&block); - // TODO: this conversion is incomplete; fix it. let mut grpc_block = GrpcBeaconBlock::new(); grpc_block.set_ssz(ssz); @@ -68,7 +77,7 @@ impl BeaconNode for BeaconBlockGrpcClient { let reply = self .client .publish_beacon_block(&req) - .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; if reply.get_success() { Ok(PublishOutcome::ValidBlock) diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs deleted file mode 100644 index 470a070e87..0000000000 --- a/validator_client/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod config; - -pub use crate::config::Config; diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index d044030fe6..0b11ed0d04 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -4,6 +4,7 @@ mod config; mod duties; pub mod error; mod service; +mod signer; use crate::config::Config as ValidatorClientConfig; use clap::{App, Arg}; diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 64ed7cb03b..9c0f5d23c1 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -9,7 +9,7 @@ /// data from the beacon node and performs the signing before publishing the block to the beacon /// node. use crate::attester_service::{AttestationGrpcClient, AttesterService}; -use crate::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; +use crate::block_producer_service::BeaconBlockGrpcClient; use crate::config::Config as ValidatorConfig; use crate::duties::UpdateOutcome; use crate::duties::{DutiesManager, EpochDutiesMap}; diff --git a/validator_client/src/signer.rs b/validator_client/src/signer.rs new file mode 100644 index 0000000000..85bf35b167 --- /dev/null +++ b/validator_client/src/signer.rs @@ -0,0 +1,7 @@ +use types::Signature; + +/// Signs message using an internally-maintained private key. +pub trait Signer { + fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option; + fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option; +} From 9cdb7bb716f375df88f185ff590c633cc18928a0 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 29 Mar 2019 17:28:07 +1100 Subject: [PATCH 15/57] Restructure of validator client service and block producer --- .../beacon_block_node.rs | 0 .../block_producer.rs | 0 .../mod.rs | 0 .../{traits.rs => beacon_node_duties.rs} | 6 +- validator_client/src/duties/grpc.rs | 8 +- validator_client/src/duties/mod.rs | 16 +- validator_client/src/main.rs | 2 +- validator_client/src/service.rs | 251 ++++++++---------- 8 files changed, 131 insertions(+), 152 deletions(-) rename validator_client/src/{block_producer_service => block_producer}/beacon_block_node.rs (100%) rename validator_client/src/{block_producer_service => block_producer}/block_producer.rs (100%) rename validator_client/src/{block_producer_service => block_producer}/mod.rs (100%) rename validator_client/src/duties/{traits.rs => beacon_node_duties.rs} (79%) diff --git a/validator_client/src/block_producer_service/beacon_block_node.rs b/validator_client/src/block_producer/beacon_block_node.rs similarity index 100% rename from validator_client/src/block_producer_service/beacon_block_node.rs rename to validator_client/src/block_producer/beacon_block_node.rs diff --git a/validator_client/src/block_producer_service/block_producer.rs b/validator_client/src/block_producer/block_producer.rs similarity index 100% rename from validator_client/src/block_producer_service/block_producer.rs rename to validator_client/src/block_producer/block_producer.rs diff --git a/validator_client/src/block_producer_service/mod.rs b/validator_client/src/block_producer/mod.rs similarity index 100% rename from validator_client/src/block_producer_service/mod.rs rename to validator_client/src/block_producer/mod.rs diff --git a/validator_client/src/duties/traits.rs b/validator_client/src/duties/beacon_node_duties.rs similarity index 79% rename from validator_client/src/duties/traits.rs rename to validator_client/src/duties/beacon_node_duties.rs index 374bed9f61..efe9e836de 100644 --- a/validator_client/src/duties/traits.rs +++ b/validator_client/src/duties/beacon_node_duties.rs @@ -2,12 +2,12 @@ use super::EpochDuties; use types::{Epoch, PublicKey}; #[derive(Debug, PartialEq, Clone)] -pub enum BeaconNodeError { +pub enum BeaconNodeDutiesError { RemoteFailure(String), } /// Defines the methods required to obtain a validators shuffling from a Beacon Node. -pub trait BeaconNode: Send + Sync { +pub trait BeaconNodeDuties: Send + Sync { /// Gets the duties for all validators. /// /// Returns a vector of EpochDuties for each validator public key. The entry will be None for @@ -16,5 +16,5 @@ pub trait BeaconNode: Send + Sync { &self, epoch: Epoch, pubkeys: &[PublicKey], - ) -> Result; + ) -> Result; } diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index 511ffa34a2..f05307141e 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -1,5 +1,5 @@ +use super::beacon_node_duties::{BeaconNodeDuties, BeaconNodeDutiesError}; use super::epoch_duties::{EpochDuties, EpochDuty}; -use super::traits::{BeaconNode, BeaconNodeError}; use grpcio::CallOption; use protos::services::{GetDutiesRequest, Validators}; use protos::services_grpc::ValidatorServiceClient; @@ -8,13 +8,13 @@ use std::collections::HashMap; use std::time::Duration; use types::{Epoch, PublicKey, Slot}; -impl BeaconNode for ValidatorServiceClient { +impl BeaconNodeDuties for ValidatorServiceClient { /// Requests all duties (block signing and committee attesting) from the Beacon Node (BN). fn request_duties( &self, epoch: Epoch, pubkeys: &[PublicKey], - ) -> Result { + ) -> Result { // Get the required duties from all validators // build the request let mut req = GetDutiesRequest::new(); @@ -29,7 +29,7 @@ impl BeaconNode for ValidatorServiceClient { // send the request, get the duties reply let reply = self .get_validator_duties(&req) - .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + .map_err(|err| BeaconNodeDutiesError::RemoteFailure(format!("{:?}", err)))?; let mut epoch_duties: HashMap> = HashMap::new(); for (index, validator_duty) in reply.get_active_validators().iter().enumerate() { diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 9fce1a3536..c16fc81fc9 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -1,13 +1,13 @@ +mod beacon_node_duties; mod epoch_duties; mod grpc; // TODO: reintroduce tests //#[cfg(test)] //mod test_node; -mod traits; +pub use self::beacon_node_duties::{BeaconNodeDuties, BeaconNodeDutiesError}; use self::epoch_duties::{EpochDuties, EpochDutiesMapError}; pub use self::epoch_duties::{EpochDutiesMap, WorkInfo}; -use self::traits::{BeaconNode, BeaconNodeError}; use futures::Async; use slog::{debug, error, info}; use std::sync::Arc; @@ -29,7 +29,7 @@ pub enum UpdateOutcome { pub enum Error { DutiesMapPoisoned, EpochMapPoisoned, - BeaconNodeError(BeaconNodeError), + BeaconNodeDutiesError(BeaconNodeDutiesError), UnknownEpoch, UnknownValidator, } @@ -38,14 +38,14 @@ pub enum Error { /// Node. /// /// This keeps track of all validator keys and required voting slots. -pub struct DutiesManager { +pub struct DutiesManager { pub duties_map: RwLock, /// A list of all public keys known to the validator service. pub pubkeys: Vec, pub beacon_node: Arc, } -impl DutiesManager { +impl DutiesManager { /// Check the Beacon Node for `EpochDuties`. /// /// be a wall-clock (e.g., system time, remote server time, etc.). @@ -112,9 +112,9 @@ impl DutiesManager { } //TODO: Use error_chain to handle errors -impl From for Error { - fn from(e: BeaconNodeError) -> Error { - Error::BeaconNodeError(e) +impl From for Error { + fn from(e: BeaconNodeDutiesError) -> Error { + Error::BeaconNodeDutiesError(e) } } diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 0b11ed0d04..6f02cf6eed 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,5 +1,5 @@ mod attester_service; -mod block_producer_service; +mod block_producer; mod config; mod duties; pub mod error; diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 9c0f5d23c1..398f6d7770 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -9,15 +9,14 @@ /// data from the beacon node and performs the signing before publishing the block to the beacon /// node. use crate::attester_service::{AttestationGrpcClient, AttesterService}; +use crate::block_producer::BlockProducer; use crate::block_producer_service::BeaconBlockGrpcClient; use crate::config::Config as ValidatorConfig; -use crate::duties::UpdateOutcome; -use crate::duties::{DutiesManager, EpochDutiesMap}; +use crate::duties::{BeaconNodeDuties, DutiesManager, EpochDutiesMap, UpdateOutcome}; 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}; use bls::Keypair; use grpcio::{ChannelBuilder, EnvBuilder}; use protos::services::Empty; @@ -35,13 +34,13 @@ use tokio::runtime::Builder; use tokio::timer::Interval; use tokio_timer::clock::Clock; use types::test_utils::generate_deterministic_keypairs; -use types::{Epoch, Fork, Slot}; +use types::{ChainSpec, Epoch, Fork, Slot}; //TODO: This service should be simplified in the future. Can be made more steamlined. /// The validator service. This is the main thread that executes and maintains validator /// duties. -pub struct Service { +pub struct Service { /// The node we currently connected to. connected_node_version: String, /// The chain id we are processing on. @@ -50,28 +49,25 @@ pub struct Service { slot_clock: SystemTimeSlotClock, /// The current slot we are processing. current_slot: Slot, - /// The number of slots per epoch to allow for converting slots to epochs. - slots_per_epoch: u64, + /// The chain specification for this clients instance. + spec: Arc, + /// The duties manager which maintains the state of when to perform actions. + duties_manager: Arc>, // GRPC Clients /// The beacon block GRPC client. - beacon_block_client: Arc, - /// The validator GRPC client. - validator_client: Arc, + beacon_block_client: Arc, /// The attester GRPC client. attester_client: Arc, /// The validator client logger. log: slog::Logger, } -impl Service { +impl Service { /// 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 /// and returns an instance of the service. - fn initialize_service( - config: &ValidatorConfig, - log: slog::Logger, - ) -> error_chain::Result { + 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()); @@ -139,7 +135,9 @@ impl Service { // Beacon node gRPC beacon block endpoints. let beacon_block_client = { let ch = ChannelBuilder::new(env.clone()).connect(&config.server); - Arc::new(BeaconBlockServiceClient::new(ch)) + let beacon_block_service_client = Arc::new(BeaconBlockServiceClient::new(ch)); + // a wrapper around the service client to implement the beacon block node trait + Arc::new(BeaconBlockGrpcClient::new(beacon_block_service_client)) }; // Beacon node gRPC validator endpoints. @@ -164,14 +162,37 @@ impl Service { .map_err(|e| ErrorKind::SlotClockError(e))? .expect("Genesis must be in the future"); + let spec = Arc::new(config.spec); + + /* Generate the duties manager */ + + // generate keypairs + + // TODO: keypairs are randomly generated; they should be loaded from a file or generated. + // https://github.com/sigp/lighthouse/issues/160 + let keypairs = Arc::new(generate_deterministic_keypairs(8)); + + // Builds a mapping of Epoch -> Map(PublicKey, EpochDuty) + // where EpochDuty contains slot numbers and attestation data that each validator needs to + // produce work on. + let duties_map = RwLock::new(EpochDutiesMap::new(config.spec.slots_per_epoch)); + + // builds a manager which maintains the list of current duties for all known validators + // and can check when a validator needs to perform a task. + let duties_manager = Arc::new(DutiesManager { + duties_map, + pubkeys: keypairs.iter().map(|keypair| keypair.pk.clone()).collect(), + beacon_node: validator_client, + }); + Ok(Self { connected_node_version: node_info.version, fork, slot_clock, current_slot, - slots_per_epoch: config.spec.slots_per_epoch, + spec, + duties_manager, beacon_block_client, - validator_client, attester_client, log, }) @@ -180,7 +201,7 @@ impl Service { /// Initialise the service then run the core thread. 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 @@ -205,137 +226,95 @@ impl Service { Interval::new(Instant::now() + duration_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 = Arc::new(generate_deterministic_keypairs(8)); - - /* build requisite objects to pass to core thread */ - - // Builds a mapping of Epoch -> Map(PublicKey, EpochDuty) - // where EpochDuty contains slot numbers and attestation data that each validator needs to - // produce work on. - let duties_map = RwLock::new(EpochDutiesMap::new(config.spec.slots_per_epoch)); - - // builds a manager which maintains the list of current duties for all known validators - // and can check when a validator needs to perform a task. - let manager = Arc::new(DutiesManager { - duties_map, - pubkeys: keypairs.iter().map(|keypair| keypair.pk.clone()).collect(), - beacon_node: service.validator_client.clone(), - }); - - // run the core thread + /* kick off the core service */ runtime.block_on( interval .for_each(move |_| { - let log = service.log.clone(); - - /* get the current slot and epoch */ - let current_slot = match service.slot_clock.present_slot() { - Err(e) => { - error!(log, "SystemTimeError {:?}", e); - return Ok(()); - } - Ok(slot) => slot.expect("Genesis is in the future"), - }; - - let current_epoch = current_slot.epoch(service.slots_per_epoch); - - debug_assert!( - current_slot > service.current_slot, - "The Timer should poll a new slot" - ); - - info!(log, "Processing"; "slot" => current_slot.as_u64(), "epoch" => current_epoch.as_u64()); - - /* check for new duties */ - - let cloned_manager = manager.clone(); - let cloned_log = log.clone(); - // spawn a new thread separate to the runtime - std::thread::spawn(move || { - let _empty_error = cloned_manager.run_update(current_epoch.clone(), cloned_log.clone()); - }); - - /* execute any specified duties */ - - if let Some(work) = manager.get_current_work(current_slot) { - for (_public_key, work_type) in work { - if work_type.produce_block { - // TODO: Produce a beacon block in a new thread - } - if work_type.attestation_duty.is_some() { - // available AttestationDuty info - let attestation_duty = - work_type.attestation_duty.expect("Cannot be None"); - //TODO: Produce an attestation in a new thread - } - } - } - + // if a non-fatal error occurs, proceed to the next slot. + let _ignore_error = service.per_slot_execution(); + // completed a slot process Ok(()) }) .map_err(|e| format!("Service thread failed: {:?}", e)), ); - - // completed a slot process + // validator client exited Ok(()) } - /* - // 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, - }; + /// The execution logic that runs every slot. + // Errors are logged to output, and core execution continues unless fatal errors occur. + fn per_slot_execution(&mut self) -> error_chain::Result<()> { + /* get the new current slot and epoch */ + self.update_current_slot()?; - block_producer_service.run(); - }) - }; + /* check for new duties */ + self.check_for_duties(); - // 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, - }; + /* process any required duties for validators */ + self.process_duties(); - attester_service.run(); - }) - }; - - threads.push((duties_manager_thread, producer_thread, attester_thread)); + Ok(()) } - // 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(); + /// Updates the known current slot and epoch. + fn update_current_slot(&mut self) -> error_chain::Result<()> { + let current_slot = match self.slot_clock.present_slot() { + Err(e) => { + error!(self.log, "SystemTimeError {:?}", e); + return Err("Could not read system time".into()); + } + Ok(slot) => slot.expect("Genesis is in the future"), + }; + + let current_epoch = current_slot.epoch(self.spec.slots_per_epoch); + + // this is a fatal error. If the slot clock repeats, there is something wrong with + // the timer, terminate immediately. + assert!( + current_slot > self.current_slot, + "The Timer should poll a new slot" + ); + self.current_slot = current_slot; + info!(self.log, "Processing"; "slot" => current_slot.as_u64(), "epoch" => current_epoch.as_u64()); + Ok(()) + } + + /// For all known validator keypairs, update any known duties from the beacon node. + fn check_for_duties(&mut self) { + let cloned_manager = self.duties_manager.clone(); + let cloned_log = self.log.clone(); + let current_epoch = self.current_slot.epoch(self.spec.slots_per_epoch); + // spawn a new thread separate to the runtime + // TODO: Handle thread termination/timeout + std::thread::spawn(move || { + // the return value is a future which returns ready. + // built to be compatible with the tokio runtime. + let _empty = cloned_manager.run_update(current_epoch.clone(), cloned_log.clone()); + }); + } + + /// If there are any duties to process, spawn a separate thread and perform required actions. + fn process_duties(&mut self) { + if let Some(work) = self.duties_manager.get_current_work(self.current_slot) { + for (_public_key, work_type) in work { + if work_type.produce_block { + // spawns a thread to produce a beacon block + std::thread::spawn(move || { + let block_producer = BlockProducer { + fork: self.fork, + slot: self.current_slot, + spec: self.spec.clone(), + }; + }); + + // TODO: Produce a beacon block in a new thread + } + if work_type.attestation_duty.is_some() { + // available AttestationDuty info + let attestation_duty = work_type.attestation_duty.expect("Cannot be None"); + //TODO: Produce an attestation in a new thread + } + } + } } - */ } From e418cd11837e31207aeca4b0ddf5004e11e89efb Mon Sep 17 00:00:00 2001 From: Age Manning Date: Fri, 29 Mar 2019 23:45:53 +1100 Subject: [PATCH 16/57] Refactor main validator service --- .../src/block_producer/block_producer.rs | 219 -------------- validator_client/src/block_producer/grpc.rs | 86 ++++++ validator_client/src/block_producer/mod.rs | 272 +++++++++++++----- validator_client/src/main.rs | 4 +- validator_client/src/service.rs | 4 +- 5 files changed, 294 insertions(+), 291 deletions(-) delete mode 100644 validator_client/src/block_producer/block_producer.rs create mode 100644 validator_client/src/block_producer/grpc.rs diff --git a/validator_client/src/block_producer/block_producer.rs b/validator_client/src/block_producer/block_producer.rs deleted file mode 100644 index e71e6cd4bb..0000000000 --- a/validator_client/src/block_producer/block_producer.rs +++ /dev/null @@ -1,219 +0,0 @@ -use super::beacon_block_node::{BeaconBlockNode, BeaconBlockNodeError}; -use crate::signer::Signer; -use ssz::{SignedRoot, TreeHash}; -use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; - -#[derive(Debug, PartialEq)] -pub enum Error { - SlotClockError, - SlotUnknowable, - EpochMapPoisoned, - SlotClockPoisoned, - EpochLengthIsZero, - BeaconBlockNodeError(BeaconBlockNodeError), -} - -#[derive(Debug, PartialEq)] -pub enum ValidatorEvent { - /// A new block was produced. - BlockProduced(Slot), - /// A block was not produced as it would have been slashable. - SlashableBlockNotProduced(Slot), - /// The Beacon Node was unable to produce a block at that slot. - BeaconNodeUnableToProduceBlock(Slot), - /// The signer failed to sign the message. - SignerRejection(Slot), - /// The public key for this validator is not an active validator. - ValidatorIsUnknown(Slot), -} - -/// This struct contains the logic for requesting and signing beacon blocks for a validator. The -/// validator can abstractly sign via the Signer trait object. -pub struct BlockProducer { - /// The current fork. - pub fork: Fork, - /// The current slot to produce a block for. - pub slot: Slot, - /// The current epoch. - pub spec: Arc, - /// The beacon node to connect to. - pub beacon_node: Arc, - /// The signer to sign the block. - pub signer: Arc, -} - -impl BlockProducer { - /// Produce a block at some slot. - /// - /// Assumes that a block is required at this slot (does not check the duties). - /// - /// Ensures the message is not slashable. - /// - /// !!! UNSAFE !!! - /// - /// The slash-protection code is not yet implemented. There is zero protection against - /// slashing. - fn produce_block(&mut self) -> Result { - let epoch = self.slot.epoch(self.spec.slots_per_epoch); - - let randao_reveal = { - let message = epoch.hash_tree_root(); - let randao_reveal = match self.signer.sign_randao_reveal( - &message, - self.spec.get_domain(epoch, Domain::Randao, &self.fork), - ) { - None => return Ok(ValidatorEvent::SignerRejection(self.slot)), - Some(signature) => signature, - }; - randao_reveal - }; - - if let Some(block) = self - .beacon_node - .produce_beacon_block(self.slot, &randao_reveal)? - { - if self.safe_to_produce(&block) { - let domain = self.spec.get_domain(epoch, Domain::BeaconBlock, &self.fork); - if let Some(block) = self.sign_block(block, domain) { - self.beacon_node.publish_beacon_block(block)?; - Ok(ValidatorEvent::BlockProduced(self.slot)) - } else { - Ok(ValidatorEvent::SignerRejection(self.slot)) - } - } else { - Ok(ValidatorEvent::SlashableBlockNotProduced(self.slot)) - } - } else { - Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(self.slot)) - } - } - - /// Consumes a block, returning that block signed by the validators private key. - /// - /// Important: this function will not check to ensure the block is not slashable. This must be - /// done upstream. - fn sign_block(&mut self, mut block: BeaconBlock, domain: u64) -> Option { - self.store_produce(&block); - - match self - .signer - .sign_block_proposal(&block.signed_root()[..], domain) - { - None => None, - Some(signature) => { - block.signature = signature; - Some(block) - } - } - } - - /// Returns `true` if signing a block is safe (non-slashable). - /// - /// !!! UNSAFE !!! - /// - /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. - fn safe_to_produce(&self, _block: &BeaconBlock) -> bool { - // TODO: ensure the producer doesn't produce slashable blocks. - // https://github.com/sigp/lighthouse/issues/160 - true - } - - /// Record that a block was produced so that slashable votes may not be made in the future. - /// - /// !!! UNSAFE !!! - /// - /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. - fn store_produce(&mut self, _block: &BeaconBlock) { - // TODO: record this block production to prevent future slashings. - // https://github.com/sigp/lighthouse/issues/160 - } -} - -impl From for Error { - fn from(e: BeaconBlockNodeError) -> Error { - Error::BeaconBlockNodeError(e) - } -} - -/* Old tests - Re-work for new logic -#[cfg(test)] -mod tests { - use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode}; - use super::*; - use slot_clock::TestingSlotClock; - use types::{ - test_utils::{SeedableRng, TestRandom, XorShiftRng}, - Keypair, - }; - - // TODO: implement more thorough testing. - // https://github.com/sigp/lighthouse/issues/160 - // - // These tests should serve as a good example for future tests. - - #[test] - pub fn polling() { - let mut rng = XorShiftRng::from_seed([42; 16]); - - let spec = Arc::new(ChainSpec::foundation()); - let slot_clock = Arc::new(TestingSlotClock::new(0)); - let beacon_node = Arc::new(SimulatedBeaconNode::default()); - let signer = Arc::new(LocalSigner::new(Keypair::random())); - - let mut epoch_map = EpochMap::new(spec.slots_per_epoch); - let produce_slot = Slot::new(100); - let produce_epoch = produce_slot.epoch(spec.slots_per_epoch); - epoch_map.map.insert(produce_epoch, produce_slot); - let epoch_map = Arc::new(epoch_map); - - let mut block_proposer = BlockProducer::new( - spec.clone(), - epoch_map.clone(), - slot_clock.clone(), - beacon_node.clone(), - signer.clone(), - ); - - // Configure responses from the BeaconNode. - beacon_node.set_next_produce_result(Ok(Some(BeaconBlock::random_for_test(&mut rng)))); - beacon_node.set_next_publish_result(Ok(PublishOutcome::ValidBlock)); - - // One slot before production slot... - slot_clock.set_slot(produce_slot.as_u64() - 1); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::BlockProductionNotRequired(produce_slot - 1)) - ); - - // On the produce slot... - slot_clock.set_slot(produce_slot.as_u64()); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::BlockProduced(produce_slot.into())) - ); - - // Trying the same produce slot again... - slot_clock.set_slot(produce_slot.as_u64()); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::SlotAlreadyProcessed(produce_slot)) - ); - - // One slot after the produce slot... - slot_clock.set_slot(produce_slot.as_u64() + 1); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::BlockProductionNotRequired(produce_slot + 1)) - ); - - // In an epoch without known duties... - let slot = (produce_epoch.as_u64() + 1) * spec.slots_per_epoch; - slot_clock.set_slot(slot); - assert_eq!( - block_proposer.poll(), - Ok(PollOutcome::ProducerDutiesUnknown(Slot::new(slot))) - ); - } -} -*/ diff --git a/validator_client/src/block_producer/grpc.rs b/validator_client/src/block_producer/grpc.rs new file mode 100644 index 0000000000..23477ece14 --- /dev/null +++ b/validator_client/src/block_producer/grpc.rs @@ -0,0 +1,86 @@ +use super::beacon_block_node::*; +use protos::services::{ + BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, +}; +use protos::services_grpc::BeaconBlockServiceClient; +use ssz::{ssz_encode, Decodable}; +use std::sync::Arc; +use types::{BeaconBlock, Signature, Slot}; + +/// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be +/// implemented upon it. +pub struct BeaconBlockGrpcClient { + client: Arc, +} + +impl BeaconBlockGrpcClient { + pub fn new(client: Arc) -> Self { + Self { client } + } +} + +impl BeaconBlockNode for BeaconBlockGrpcClient { + /// Request a Beacon Node (BN) to produce a new block at the supplied slot. + /// + /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the + /// BN is unable to find a parent block. + fn produce_beacon_block( + &self, + slot: Slot, + randao_reveal: &Signature, + ) -> Result, BeaconBlockNodeError> { + // request a beacon block from the node + let mut req = ProduceBeaconBlockRequest::new(); + req.set_slot(slot.as_u64()); + req.set_randao_reveal(ssz_encode(randao_reveal)); + + //TODO: Determine if we want an explicit timeout + let reply = self + .client + .produce_beacon_block(&req) + .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + + // format the reply + if reply.has_block() { + let block = reply.get_block(); + let ssz = block.get_ssz(); + + let (block, _i) = BeaconBlock::ssz_decode(&ssz, 0) + .map_err(|_| BeaconBlockNodeError::DecodeFailure)?; + + Ok(Some(block)) + } else { + Ok(None) + } + } + + /// Request a Beacon Node (BN) to publish a block. + /// + /// Generally, this will be called after a `produce_beacon_block` call with a block that has + /// been completed (signed) by the validator client. + fn publish_beacon_block( + &self, + block: BeaconBlock, + ) -> Result { + let mut req = PublishBeaconBlockRequest::new(); + + let ssz = ssz_encode(&block); + + let mut grpc_block = GrpcBeaconBlock::new(); + grpc_block.set_ssz(ssz); + + req.set_block(grpc_block); + + let reply = self + .client + .publish_beacon_block(&req) + .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + + if reply.get_success() { + Ok(PublishOutcome::ValidBlock) + } else { + // TODO: distinguish between different errors + Ok(PublishOutcome::InvalidBlock("Publish failed".to_string())) + } + } +} diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index fe34f627d8..8297469baf 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -1,89 +1,223 @@ mod beacon_block_node; -mod block_producer; +mod grpc; -use self::beacon_block_node::*; -use protos::services::{ - BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, -}; -use protos::services_grpc::BeaconBlockServiceClient; -use ssz::{ssz_encode, Decodable}; +use self::beacon_block_node::{BeaconBlockNode, BeaconBlockNodeError}; +pub use self::grpc::BeaconBlockGrpcClient; +use crate::signer::Signer; +use ssz::{SignedRoot, TreeHash}; use std::sync::Arc; -use types::{BeaconBlock, Signature, Slot}; +use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; -/// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be -/// implemented upon it. -pub struct BeaconBlockGrpcClient { - client: Arc, +#[derive(Debug, PartialEq)] +pub enum Error { + SlotClockError, + SlotUnknowable, + EpochMapPoisoned, + SlotClockPoisoned, + EpochLengthIsZero, + BeaconBlockNodeError(BeaconBlockNodeError), } -impl BeaconBlockGrpcClient { - pub fn new(client: Arc) -> Self { - Self { client } - } +#[derive(Debug, PartialEq)] +pub enum ValidatorEvent { + /// A new block was produced. + BlockProduced(Slot), + /// A block was not produced as it would have been slashable. + SlashableBlockNotProduced(Slot), + /// The Beacon Node was unable to produce a block at that slot. + BeaconNodeUnableToProduceBlock(Slot), + /// The signer failed to sign the message. + SignerRejection(Slot), + /// The public key for this validator is not an active validator. + ValidatorIsUnknown(Slot), } -impl BeaconBlockNode for BeaconBlockGrpcClient { - /// Request a Beacon Node (BN) to produce a new block at the supplied slot. +/// This struct contains the logic for requesting and signing beacon blocks for a validator. The +/// validator can abstractly sign via the Signer trait object. +pub struct BlockProducer { + /// The current fork. + pub fork: Fork, + /// The current slot to produce a block for. + pub slot: Slot, + /// The current epoch. + pub spec: Arc, + /// The beacon node to connect to. + pub beacon_node: Arc, + /// The signer to sign the block. + pub signer: Arc, +} + +impl BlockProducer { + /// Produce a block at some slot. /// - /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the - /// BN is unable to find a parent block. - fn produce_beacon_block( - &self, - slot: Slot, - randao_reveal: &Signature, - ) -> Result, BeaconBlockNodeError> { - // request a beacon block from the node - let mut req = ProduceBeaconBlockRequest::new(); - req.set_slot(slot.as_u64()); - req.set_randao_reveal(ssz_encode(randao_reveal)); + /// Assumes that a block is required at this slot (does not check the duties). + /// + /// Ensures the message is not slashable. + /// + /// !!! UNSAFE !!! + /// + /// The slash-protection code is not yet implemented. There is zero protection against + /// slashing. + fn produce_block(&mut self) -> Result { + let epoch = self.slot.epoch(self.spec.slots_per_epoch); - //TODO: Determine if we want an explicit timeout - let reply = self - .client - .produce_beacon_block(&req) - .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + let randao_reveal = { + let message = epoch.hash_tree_root(); + let randao_reveal = match self.signer.sign_randao_reveal( + &message, + self.spec.get_domain(epoch, Domain::Randao, &self.fork), + ) { + None => return Ok(ValidatorEvent::SignerRejection(self.slot)), + Some(signature) => signature, + }; + randao_reveal + }; - // format the reply - if reply.has_block() { - let block = reply.get_block(); - let ssz = block.get_ssz(); - - let (block, _i) = BeaconBlock::ssz_decode(&ssz, 0) - .map_err(|_| BeaconBlockNodeError::DecodeFailure)?; - - Ok(Some(block)) + if let Some(block) = self + .beacon_node + .produce_beacon_block(self.slot, &randao_reveal)? + { + if self.safe_to_produce(&block) { + let domain = self.spec.get_domain(epoch, Domain::BeaconBlock, &self.fork); + if let Some(block) = self.sign_block(block, domain) { + self.beacon_node.publish_beacon_block(block)?; + Ok(ValidatorEvent::BlockProduced(self.slot)) + } else { + Ok(ValidatorEvent::SignerRejection(self.slot)) + } + } else { + Ok(ValidatorEvent::SlashableBlockNotProduced(self.slot)) + } } else { - Ok(None) + Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(self.slot)) } } - /// Request a Beacon Node (BN) to publish a block. + /// Consumes a block, returning that block signed by the validators private key. /// - /// Generally, this will be called after a `produce_beacon_block` call with a block that has - /// been completed (signed) by the validator client. - fn publish_beacon_block( - &self, - block: BeaconBlock, - ) -> Result { - let mut req = PublishBeaconBlockRequest::new(); + /// Important: this function will not check to ensure the block is not slashable. This must be + /// done upstream. + fn sign_block(&mut self, mut block: BeaconBlock, domain: u64) -> Option { + self.store_produce(&block); - let ssz = ssz_encode(&block); - - let mut grpc_block = GrpcBeaconBlock::new(); - grpc_block.set_ssz(ssz); - - req.set_block(grpc_block); - - let reply = self - .client - .publish_beacon_block(&req) - .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; - - if reply.get_success() { - Ok(PublishOutcome::ValidBlock) - } else { - // TODO: distinguish between different errors - Ok(PublishOutcome::InvalidBlock("Publish failed".to_string())) + match self + .signer + .sign_block_proposal(&block.signed_root()[..], domain) + { + None => None, + Some(signature) => { + block.signature = signature; + Some(block) + } } } + + /// Returns `true` if signing a block is safe (non-slashable). + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn safe_to_produce(&self, _block: &BeaconBlock) -> bool { + // TODO: ensure the producer doesn't produce slashable blocks. + // https://github.com/sigp/lighthouse/issues/160 + true + } + + /// Record that a block was produced so that slashable votes may not be made in the future. + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn store_produce(&mut self, _block: &BeaconBlock) { + // TODO: record this block production to prevent future slashings. + // https://github.com/sigp/lighthouse/issues/160 + } } + +impl From for Error { + fn from(e: BeaconBlockNodeError) -> Error { + Error::BeaconBlockNodeError(e) + } +} + +/* Old tests - Re-work for new logic +#[cfg(test)] +mod tests { + use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode}; + use super::*; + use slot_clock::TestingSlotClock; + use types::{ + test_utils::{SeedableRng, TestRandom, XorShiftRng}, + Keypair, + }; + + // TODO: implement more thorough testing. + // https://github.com/sigp/lighthouse/issues/160 + // + // These tests should serve as a good example for future tests. + + #[test] + pub fn polling() { + let mut rng = XorShiftRng::from_seed([42; 16]); + + let spec = Arc::new(ChainSpec::foundation()); + let slot_clock = Arc::new(TestingSlotClock::new(0)); + let beacon_node = Arc::new(SimulatedBeaconNode::default()); + let signer = Arc::new(LocalSigner::new(Keypair::random())); + + let mut epoch_map = EpochMap::new(spec.slots_per_epoch); + let produce_slot = Slot::new(100); + let produce_epoch = produce_slot.epoch(spec.slots_per_epoch); + epoch_map.map.insert(produce_epoch, produce_slot); + let epoch_map = Arc::new(epoch_map); + + let mut block_proposer = BlockProducer::new( + spec.clone(), + epoch_map.clone(), + slot_clock.clone(), + beacon_node.clone(), + signer.clone(), + ); + + // Configure responses from the BeaconNode. + beacon_node.set_next_produce_result(Ok(Some(BeaconBlock::random_for_test(&mut rng)))); + beacon_node.set_next_publish_result(Ok(PublishOutcome::ValidBlock)); + + // One slot before production slot... + slot_clock.set_slot(produce_slot.as_u64() - 1); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProductionNotRequired(produce_slot - 1)) + ); + + // On the produce slot... + slot_clock.set_slot(produce_slot.as_u64()); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProduced(produce_slot.into())) + ); + + // Trying the same produce slot again... + slot_clock.set_slot(produce_slot.as_u64()); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::SlotAlreadyProcessed(produce_slot)) + ); + + // One slot after the produce slot... + slot_clock.set_slot(produce_slot.as_u64() + 1); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::BlockProductionNotRequired(produce_slot + 1)) + ); + + // In an epoch without known duties... + let slot = (produce_epoch.as_u64() + 1) * spec.slots_per_epoch; + slot_clock.set_slot(slot); + assert_eq!( + block_proposer.poll(), + Ok(PollOutcome::ProducerDutiesUnknown(Slot::new(slot))) + ); + } +} +*/ diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 6f02cf6eed..b3a488fe08 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -8,6 +8,7 @@ mod signer; use crate::config::Config as ValidatorClientConfig; use clap::{App, Arg}; +use protos::services_grpc::ValidatorServiceClient; use service::Service as ValidatorService; use slog::{error, info, o, Drain}; @@ -53,7 +54,8 @@ fn main() { .expect("Unable to build a configuration for the validator client."); // start the validator service. - match ValidatorService::start(config, log.clone()) { + // this specifies the GRPC type to use as the duty manager beacon node. + match ValidatorService::::start(config, log.clone()) { Ok(_) => info!(log, "Validator client shutdown successfully."), 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 398f6d7770..c8874a1f62 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -9,8 +9,7 @@ /// data from the beacon node and performs the signing before publishing the block to the beacon /// node. use crate::attester_service::{AttestationGrpcClient, AttesterService}; -use crate::block_producer::BlockProducer; -use crate::block_producer_service::BeaconBlockGrpcClient; +use crate::block_producer::{BeaconBlockGrpcClient, BlockProducer}; use crate::config::Config as ValidatorConfig; use crate::duties::{BeaconNodeDuties, DutiesManager, EpochDutiesMap, UpdateOutcome}; use crate::error as error_chain; @@ -40,6 +39,7 @@ use types::{ChainSpec, Epoch, Fork, Slot}; /// The validator service. This is the main thread that executes and maintains validator /// duties. +//TODO: Generalize the BeaconNode types to use testing pub struct Service { /// The node we currently connected to. connected_node_version: String, From 97bb61371c666deae630fe6eb9006119168f8b9e Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 12:14:56 +1100 Subject: [PATCH 17/57] Correct compiler issues, re-introduce validator library --- validator_client/Cargo.toml | 4 ++++ validator_client/src/lib.rs | 3 +++ validator_client/src/service.rs | 25 ++++++++++++++----------- 3 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 validator_client/src/lib.rs diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 209ebf25e7..80477c8eaa 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -8,6 +8,10 @@ edition = "2018" name = "validator_client" path = "src/main.rs" +[lib] +name = "validator_client" +path = "src/lib.rs" + [dependencies] block_proposer = { path = "../eth2/block_proposer" } attester = { path = "../eth2/attester" } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs new file mode 100644 index 0000000000..470a070e87 --- /dev/null +++ b/validator_client/src/lib.rs @@ -0,0 +1,3 @@ +pub mod config; + +pub use crate::config::Config; diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index c8874a1f62..bd1053a342 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -41,9 +41,7 @@ use types::{ChainSpec, Epoch, Fork, Slot}; /// duties. //TODO: Generalize the BeaconNode types to use testing pub struct Service { - /// The node we currently connected to. - connected_node_version: String, - /// The chain id we are processing on. + /// The node's current fork version we are processing on. fork: Fork, /// The slot clock for this service. slot_clock: SystemTimeSlotClock, @@ -67,7 +65,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) -> error_chain::Result { + 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()); @@ -162,8 +163,6 @@ impl Service { .map_err(|e| ErrorKind::SlotClockError(e))? .expect("Genesis must be in the future"); - let spec = Arc::new(config.spec); - /* Generate the duties manager */ // generate keypairs @@ -185,8 +184,9 @@ impl Service { beacon_node: validator_client, }); - Ok(Self { - connected_node_version: node_info.version, + let spec = Arc::new(config.spec); + + Ok(Service { fork, slot_clock, current_slot, @@ -199,9 +199,10 @@ impl Service { } /// Initialise the service then run the core thread. + // TODO: Improve handling of generic BeaconNode types, to stub grpcClient 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 mut service = Service::::initialize_service(config, log)?; // we have connected to a node and established its parameters. Spin up the core service @@ -221,7 +222,7 @@ impl Service { // set up the validator work interval - start at next slot and proceed every slot 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); + let slot_duration = Duration::from_secs(service.spec.seconds_per_slot); //TODO: Handle checked add correctly Interval::new(Instant::now() + duration_to_next_slot, slot_duration) }; @@ -236,7 +237,7 @@ impl Service { Ok(()) }) .map_err(|e| format!("Service thread failed: {:?}", e)), - ); + )?; // validator client exited Ok(()) } @@ -300,11 +301,13 @@ impl Service { if work_type.produce_block { // spawns a thread to produce a beacon block std::thread::spawn(move || { + /* let block_producer = BlockProducer { fork: self.fork, slot: self.current_slot, spec: self.spec.clone(), }; + */ }); // TODO: Produce a beacon block in a new thread From d3a6d73153eb6de71ed6eb5d2a8ad08324bf5927 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 14:27:37 +1100 Subject: [PATCH 18/57] Implements Signer generic for validator client and epoch duties --- eth2/utils/bls/src/keypair.rs | 7 +++++ .../src/duties/beacon_node_duties.rs | 4 +-- validator_client/src/duties/epoch_duties.rs | 6 ++-- validator_client/src/duties/grpc.rs | 12 ++++---- validator_client/src/duties/mod.rs | 26 +++++++++-------- validator_client/src/main.rs | 5 ++-- validator_client/src/service.rs | 17 +++++------ validator_client/src/signer.rs | 29 +++++++++++++++++-- 8 files changed, 70 insertions(+), 36 deletions(-) diff --git a/eth2/utils/bls/src/keypair.rs b/eth2/utils/bls/src/keypair.rs index c91b13bad3..2f0e794a61 100644 --- a/eth2/utils/bls/src/keypair.rs +++ b/eth2/utils/bls/src/keypair.rs @@ -1,5 +1,6 @@ use super::{PublicKey, SecretKey}; use serde_derive::{Deserialize, Serialize}; +use std::fmt; use std::hash::{Hash, Hasher}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -32,3 +33,9 @@ impl Hash for Keypair { self.pk.as_uncompressed_bytes().hash(state) } } + +impl fmt::Display for Keypair { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.pk) + } +} diff --git a/validator_client/src/duties/beacon_node_duties.rs b/validator_client/src/duties/beacon_node_duties.rs index b66b5f704f..af1fab60bf 100644 --- a/validator_client/src/duties/beacon_node_duties.rs +++ b/validator_client/src/duties/beacon_node_duties.rs @@ -1,5 +1,5 @@ use super::EpochDuties; -use types::{Epoch, Keypair}; +use types::{Epoch, PublicKey}; #[derive(Debug, PartialEq, Clone)] pub enum BeaconNodeDutiesError { @@ -15,6 +15,6 @@ pub trait BeaconNodeDuties: Send + Sync { fn request_duties( &self, epoch: Epoch, - signers: &[Keypair], + pub_keys: &[PublicKey], ) -> Result; } diff --git a/validator_client/src/duties/epoch_duties.rs b/validator_client/src/duties/epoch_duties.rs index 8e710ba9aa..0a4f73f723 100644 --- a/validator_client/src/duties/epoch_duties.rs +++ b/validator_client/src/duties/epoch_duties.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::fmt; use std::ops::{Deref, DerefMut}; -use types::{AttestationDuty, Epoch, Keypair, Slot}; +use types::{AttestationDuty, Epoch, PublicKey, Slot}; /// When work needs to be performed by a validator, this type is given back to the main service /// which indicates all the information that required to process the work. @@ -72,7 +72,7 @@ impl fmt::Display for EpochDuty { } /// Maps a list of keypairs (many validators) to an EpochDuty. -pub type EpochDuties = HashMap>; +pub type EpochDuties = HashMap>; pub enum EpochDutiesMapError { UnknownEpoch, @@ -113,7 +113,7 @@ impl EpochDutiesMap { pub fn is_work_slot( &self, slot: Slot, - signer: &Keypair, + signer: &PublicKey, ) -> Result, EpochDutiesMapError> { let epoch = slot.epoch(self.slots_per_epoch); diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index d6e2e5238e..66bf368a21 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -6,21 +6,21 @@ use protos::services_grpc::ValidatorServiceClient; use ssz::ssz_encode; use std::collections::HashMap; use std::time::Duration; -use types::{Epoch, Keypair, Slot}; +use types::{Epoch, PublicKey, Slot}; impl BeaconNodeDuties for ValidatorServiceClient { /// Requests all duties (block signing and committee attesting) from the Beacon Node (BN). fn request_duties( &self, epoch: Epoch, - signers: &[Keypair], + pub_keys: &[PublicKey], ) -> Result { // Get the required duties from all validators // build the request let mut req = GetDutiesRequest::new(); req.set_epoch(epoch.as_u64()); let mut validators = Validators::new(); - validators.set_public_keys(signers.iter().map(|v| ssz_encode(&v.pk)).collect()); + validators.set_public_keys(pub_keys.iter().map(|v| ssz_encode(v)).collect()); req.set_validators(validators); // set a timeout for requests @@ -31,11 +31,11 @@ impl BeaconNodeDuties for ValidatorServiceClient { .get_validator_duties(&req) .map_err(|err| BeaconNodeDutiesError::RemoteFailure(format!("{:?}", err)))?; - let mut epoch_duties: HashMap> = HashMap::new(); + let mut epoch_duties: HashMap> = HashMap::new(); for (index, validator_duty) in reply.get_active_validators().iter().enumerate() { if !validator_duty.has_duty() { // validator is inactive - epoch_duties.insert(signers[index].clone(), None); + epoch_duties.insert(pub_keys[index].clone(), None); continue; } // active validator @@ -53,7 +53,7 @@ impl BeaconNodeDuties for ValidatorServiceClient { attestation_shard: active_duty.get_attestation_shard(), committee_index: active_duty.get_committee_index(), }; - epoch_duties.insert(signers[index].clone(), Some(epoch_duty)); + epoch_duties.insert(pub_keys[index].clone(), Some(epoch_duty)); } Ok(epoch_duties) } diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 1019f489dc..596d314ed4 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -8,11 +8,13 @@ mod grpc; pub use self::beacon_node_duties::{BeaconNodeDuties, BeaconNodeDutiesError}; use self::epoch_duties::{EpochDuties, EpochDutiesMapError}; pub use self::epoch_duties::{EpochDutiesMap, WorkInfo}; +use super::signer::Signer; use futures::Async; use slog::{debug, error, info}; +use std::fmt::Display; use std::sync::Arc; use std::sync::RwLock; -use types::{Epoch, Keypair, Slot}; +use types::{Epoch, PublicKey, Slot}; #[derive(Debug, PartialEq, Clone)] pub enum UpdateOutcome { @@ -38,20 +40,20 @@ pub enum Error { /// Node. /// /// This keeps track of all validator keys and required voting slots. -pub struct DutiesManager { +pub struct DutiesManager { pub duties_map: RwLock, /// A list of all signer objects known to the validator service. - // TODO: Generalise the signers, so that they're not just keypairs - pub signers: Arc>, + pub signers: Arc>, pub beacon_node: Arc, } -impl DutiesManager { +impl DutiesManager { /// Check the Beacon Node for `EpochDuties`. /// /// be a wall-clock (e.g., system time, remote server time, etc.). fn update(&self, epoch: Epoch) -> Result { - let duties = self.beacon_node.request_duties(epoch, &self.signers)?; + let public_keys: Vec = self.signers.iter().map(|s| s.to_public()).collect(); + let duties = self.beacon_node.request_duties(epoch, &public_keys)?; { // If these duties were known, check to see if they're updates or identical. if let Some(known_duties) = self.duties_map.read()?.get(&epoch) { @@ -90,17 +92,17 @@ impl DutiesManager { Ok(Async::Ready(())) } - /// Returns a list of (Public, WorkInfo) indicating all the validators that have work to perform + /// Returns a list of (index, WorkInfo) indicating all the validators that have work to perform /// this slot. - pub fn get_current_work(&self, slot: Slot) -> Option> { - let mut current_work: Vec<(Keypair, WorkInfo)> = Vec::new(); + pub fn get_current_work(&self, slot: Slot) -> Option> { + let mut current_work: Vec<(usize, WorkInfo)> = Vec::new(); // if the map is poisoned, return None let duties = self.duties_map.read().ok()?; - for validator_signer in self.signers.iter() { - match duties.is_work_slot(slot, &validator_signer) { - Ok(Some(work_type)) => current_work.push((validator_signer.clone(), work_type)), + for (index, validator_signer) in self.signers.iter().enumerate() { + match duties.is_work_slot(slot, &validator_signer.to_public()) { + Ok(Some(work_type)) => current_work.push((index, work_type)), Ok(None) => {} // No work for this validator //TODO: This should really log an error, as we shouldn't end up with an err here. Err(_) => {} // Unknown epoch or validator, no work diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index b3a488fe08..4817667348 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -11,6 +11,7 @@ use clap::{App, Arg}; use protos::services_grpc::ValidatorServiceClient; use service::Service as ValidatorService; use slog::{error, info, o, Drain}; +use types::Keypair; fn main() { // Logging @@ -54,8 +55,8 @@ fn main() { .expect("Unable to build a configuration for the validator client."); // start the validator service. - // this specifies the GRPC type to use as the duty manager beacon node. - match ValidatorService::::start(config, log.clone()) { + // this specifies the GRPC and signer type to use as the duty manager beacon node. + match ValidatorService::::start(config, log.clone()) { Ok(_) => info!(log, "Validator client shutdown successfully."), 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 d92d944cb7..d6c0e6638b 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -14,6 +14,7 @@ use crate::config::Config as ValidatorConfig; use crate::duties::{BeaconNodeDuties, DutiesManager, EpochDutiesMap, UpdateOutcome}; use crate::error as error_chain; use crate::error::ErrorKind; +use crate::signer::Signer; use attester::test_utils::EpochMap; use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; use bls::Keypair; @@ -36,14 +37,10 @@ use tokio_timer::clock::Clock; use types::test_utils::generate_deterministic_keypairs; use types::{ChainSpec, Epoch, Fork, Slot}; -//TODO: This service should be simplified in the future. Can be made more steamlined. - -const POLL_INTERVAL_MILLIS: u64 = 100; - /// The validator service. This is the main thread that executes and maintains validator /// duties. //TODO: Generalize the BeaconNode types to use testing -pub struct Service { +pub struct Service { /// The node's current fork version we are processing on. fork: Fork, /// The slot clock for this service. @@ -53,7 +50,7 @@ pub struct Service { /// The chain specification for this clients instance. spec: Arc, /// The duties manager which maintains the state of when to perform actions. - duties_manager: Arc>, + duties_manager: Arc>, // GRPC Clients /// The beacon block GRPC client. beacon_block_client: Arc, @@ -63,7 +60,7 @@ pub struct Service { log: slog::Logger, } -impl Service { +impl Service { /// 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 @@ -71,7 +68,7 @@ impl Service { fn initialize_service( config: ValidatorConfig, log: slog::Logger, - ) -> error_chain::Result> { + ) -> error_chain::Result> { // initialise the beacon node client to check for a connection let env = Arc::new(EnvBuilder::new().build()); @@ -183,6 +180,7 @@ impl Service { // and can check when a validator needs to perform a task. let duties_manager = Arc::new(DutiesManager { duties_map, + // these are abstract objects capable of signing signers: keypairs, beacon_node: validator_client, }); @@ -205,7 +203,8 @@ impl Service { // TODO: Improve handling of generic BeaconNode types, to stub grpcClient 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 mut service = Service::::initialize_service(config, log)?; + let mut service = + Service::::initialize_service(config, log)?; // we have connected to a node and established its parameters. Spin up the core service diff --git a/validator_client/src/signer.rs b/validator_client/src/signer.rs index 85bf35b167..49dedbb33f 100644 --- a/validator_client/src/signer.rs +++ b/validator_client/src/signer.rs @@ -1,7 +1,32 @@ -use types::Signature; +use std::fmt::Display; +use types::{Keypair, PublicKey, Signature}; /// Signs message using an internally-maintained private key. -pub trait Signer { +pub trait Signer: Display + Send + Sync { fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option; fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option; + /// Returns a public key for the signer object. + fn to_public(&self) -> PublicKey; +} + +/* Implements Display and Signer for Keypair */ + +impl Signer for Keypair { + fn to_public(&self) -> PublicKey { + self.pk.clone() + } + + fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option { + Some(Signature::new(message, domain, &self.sk)) + } + + fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option { + Some(Signature::new(message, domain, &self.sk)) + } + + /* + fn sign_attestation_message(&self, message: &[u8], domain: u64) -> Option { + Some(Signature::new(message, domain, &self.sk)) + } + */ } From deb0abd4a811a3c33491a9476755f5466953f010 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 14:28:42 +1100 Subject: [PATCH 19/57] Restores display for validator keys --- validator_client/src/duties/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 596d314ed4..9f0998567f 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -140,9 +140,9 @@ impl From for Error { fn print_duties(log: &slog::Logger, duties: EpochDuties) { for (pk, duty) in duties.iter() { if let Some(display_duty) = duty { - info!(log, "Validator: {:?}",pk; "Duty" => format!("{}",display_duty)); + info!(log, "Validator: {}",pk; "Duty" => format!("{}",display_duty)); } else { - info!(log, "Validator: {:?}",pk; "Duty" => "None"); + info!(log, "Validator: {}",pk; "Duty" => "None"); } } } From ba9090173023e48d36844ea224bcfafabec57ce5 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 14:48:43 +1100 Subject: [PATCH 20/57] Referenced signer passed to block producer --- validator_client/src/block_producer/mod.rs | 11 +++-------- validator_client/src/service.rs | 18 ++++++++++++------ validator_client/src/signer.rs | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index 8297469baf..77b7196667 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -10,11 +10,6 @@ use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; #[derive(Debug, PartialEq)] pub enum Error { - SlotClockError, - SlotUnknowable, - EpochMapPoisoned, - SlotClockPoisoned, - EpochLengthIsZero, BeaconBlockNodeError(BeaconBlockNodeError), } @@ -34,7 +29,7 @@ pub enum ValidatorEvent { /// This struct contains the logic for requesting and signing beacon blocks for a validator. The /// validator can abstractly sign via the Signer trait object. -pub struct BlockProducer { +pub struct BlockProducer<'a, B: BeaconBlockNode, S: Signer> { /// The current fork. pub fork: Fork, /// The current slot to produce a block for. @@ -44,10 +39,10 @@ pub struct BlockProducer { /// The beacon node to connect to. pub beacon_node: Arc, /// The signer to sign the block. - pub signer: Arc, + pub signer: &'a S, } -impl BlockProducer { +impl<'a, B: BeaconBlockNode, S: Signer> BlockProducer<'a, B, S> { /// Produce a block at some slot. /// /// Assumes that a block is required at this slot (does not check the duties). diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index d6c0e6638b..4c01d89671 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -299,17 +299,23 @@ impl Service { /// If there are any duties to process, spawn a separate thread and perform required actions. fn process_duties(&mut self) { if let Some(work) = self.duties_manager.get_current_work(self.current_slot) { - for (_public_key, work_type) in work { + for (signer_index, work_type) in work { if work_type.produce_block { // spawns a thread to produce a beacon block + let signers = self.duties_manager.signers.clone(); + let fork = self.fork.clone(); + let slot = self.current_slot.clone(); + let spec = self.spec.clone(); + let beacon_node = self.beacon_block_client.clone(); std::thread::spawn(move || { - /* + let signer = &signers[signer_index]; let block_producer = BlockProducer { - fork: self.fork, - slot: self.current_slot, - spec: self.spec.clone(), + fork, + slot, + spec, + beacon_node, + signer, }; - */ }); // TODO: Produce a beacon block in a new thread diff --git a/validator_client/src/signer.rs b/validator_client/src/signer.rs index 49dedbb33f..4bbada08ee 100644 --- a/validator_client/src/signer.rs +++ b/validator_client/src/signer.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use types::{Keypair, PublicKey, Signature}; /// Signs message using an internally-maintained private key. -pub trait Signer: Display + Send + Sync { +pub trait Signer: Display + Send + Sync + Clone { fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option; fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option; /// Returns a public key for the signer object. From 6e254551af043f39dadbb7070220f81dc3d0f7f5 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 15:58:31 +1100 Subject: [PATCH 21/57] Implement produce beacon block on gRPC beacon node server --- beacon_node/beacon_chain/src/lib.rs | 2 +- beacon_node/rpc/src/beacon_block.rs | 58 +++++++++++++++---- beacon_node/rpc/src/beacon_chain.rs | 15 ++++- .../testing_beacon_state_builder.rs | 2 +- validator_client/src/block_producer/mod.rs | 24 +++++++- validator_client/src/service.rs | 7 ++- 6 files changed, 90 insertions(+), 18 deletions(-) diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 48a42b941e..234a960945 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -7,7 +7,7 @@ pub mod test_utils; pub use self::beacon_chain::{BeaconChain, BlockProcessingOutcome, InvalidBlock, ValidBlock}; pub use self::checkpoint::CheckPoint; -pub use self::errors::BeaconChainError; +pub use self::errors::{BeaconChainError, BlockProductionError}; pub use attestation_aggregator::Outcome as AggregationOutcome; pub use db; pub use fork_choice; diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index f6b426c18f..e8b3cb01b4 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -3,7 +3,7 @@ use crossbeam_channel; use eth2_libp2p::rpc::methods::BlockRootSlot; use eth2_libp2p::PubsubMessage; use futures::Future; -use grpcio::{RpcContext, UnarySink}; +use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink}; use network::NetworkMessage; use protos::services::{ BeaconBlock as BeaconBlockProto, ProduceBeaconBlockRequest, ProduceBeaconBlockResponse, @@ -11,10 +11,10 @@ use protos::services::{ }; use protos::services_grpc::BeaconBlockService; use slog::Logger; -use slog::{debug, error, info, warn}; -use ssz::{Decodable, TreeHash}; +use slog::{error, info, trace, warn}; +use ssz::{ssz_encode, Decodable, TreeHash}; use std::sync::Arc; -use types::{BeaconBlock, Hash256, Slot}; +use types::{BeaconBlock, Hash256, Signature, Slot}; #[derive(Clone)] pub struct BeaconBlockServiceInstance { @@ -31,11 +31,44 @@ impl BeaconBlockService for BeaconBlockServiceInstance { req: ProduceBeaconBlockRequest, sink: UnarySink, ) { - println!("producing at slot {}", req.get_slot()); + trace!(self.log, "Generating a beacon block"; "req" => format!("{:?}", req)); + + // decode the request + // TODO: requested slot currently unused, see: https://github.com/sigp/lighthouse/issues/336 + let _requested_slot = Slot::from(req.get_slot()); + let (randao_reveal, _index) = match Signature::ssz_decode(req.get_randao_reveal(), 0) { + Ok(v) => v, + Err(_) => { + // decode error, incorrect signature + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::InvalidArgument, + Some(format!("Invalid randao reveal signature")), + )) + .map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e)); + return ctx.spawn(f); + } + }; + + let produced_block = match self.chain.produce_block(randao_reveal) { + Ok((block, _state)) => block, + Err(e) => { + // could not produce a block + let log_clone = self.log.clone(); + warn!(self.log, "RPC Error"; "Error" => format!("Could not produce a block:{:?}",e)); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::Unknown, + Some(format!("Could not produce a block: {:?}", e)), + )) + .map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e)); + return ctx.spawn(f); + } + }; - // TODO: build a legit block. let mut block = BeaconBlockProto::new(); - block.set_ssz(b"cats".to_vec()); + block.set_ssz(ssz_encode(&produced_block)); let mut resp = ProduceBeaconBlockResponse::new(); resp.set_block(block); @@ -81,11 +114,16 @@ impl BeaconBlockService for BeaconBlockServiceInstance { slot: block.slot, }); - println!("Sending beacon block to gossipsub"); - self.network_chan.send(NetworkMessage::Publish { + match self.network_chan.send(NetworkMessage::Publish { topics: vec![topic], message, - }); + }) { + Ok(_) => {} + Err(_) => warn!( + self.log, + "Could not send published block to the network service" + ), + } resp.set_success(true); } else if outcome.is_invalid() { diff --git a/beacon_node/rpc/src/beacon_chain.rs b/beacon_node/rpc/src/beacon_chain.rs index 0551a80246..f21b8df7b4 100644 --- a/beacon_node/rpc/src/beacon_chain.rs +++ b/beacon_node/rpc/src/beacon_chain.rs @@ -4,7 +4,8 @@ use beacon_chain::{ fork_choice::ForkChoice, parking_lot::RwLockReadGuard, slot_clock::SlotClock, - types::{BeaconState, ChainSpec}, + types::{BeaconState, ChainSpec, Signature}, + BlockProductionError, }; pub use beacon_chain::{BeaconChainError, BlockProcessingOutcome}; use types::BeaconBlock; @@ -17,6 +18,11 @@ pub trait BeaconChain: Send + Sync { fn process_block(&self, block: BeaconBlock) -> Result; + + fn produce_block( + &self, + randao_reveal: Signature, + ) -> Result<(BeaconBlock, BeaconState), BlockProductionError>; } impl BeaconChain for RawBeaconChain @@ -39,4 +45,11 @@ where ) -> Result { self.process_block(block) } + + fn produce_block( + &self, + randao_reveal: Signature, + ) -> Result<(BeaconBlock, BeaconState), BlockProductionError> { + self.produce_block(randao_reveal) + } } diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 5e4cebd576..7c231b20b0 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -120,7 +120,7 @@ impl TestingBeaconStateBuilder { }) .collect(); - let genesis_time = 1553776331; // arbitrary + let genesis_time = 1553918534; // arbitrary let mut state = BeaconState::genesis( genesis_time, diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index 77b7196667..7def97e03c 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -4,6 +4,7 @@ mod grpc; use self::beacon_block_node::{BeaconBlockNode, BeaconBlockNodeError}; pub use self::grpc::BeaconBlockGrpcClient; use crate::signer::Signer; +use slog::{error, info}; use ssz::{SignedRoot, TreeHash}; use std::sync::Arc; use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; @@ -23,8 +24,6 @@ pub enum ValidatorEvent { BeaconNodeUnableToProduceBlock(Slot), /// The signer failed to sign the message. SignerRejection(Slot), - /// The public key for this validator is not an active validator. - ValidatorIsUnknown(Slot), } /// This struct contains the logic for requesting and signing beacon blocks for a validator. The @@ -43,6 +42,25 @@ pub struct BlockProducer<'a, B: BeaconBlockNode, S: Signer> { } impl<'a, B: BeaconBlockNode, S: Signer> BlockProducer<'a, B, S> { + /// Handle outputs and results from block production. + pub fn handle_produce_block(&mut self, log: slog::Logger) { + match self.produce_block() { + Ok(ValidatorEvent::BlockProduced(_slot)) => { + info!(log, "Block produced"; "Validator" => format!("{}", self.signer)) + } + Err(e) => error!(log, "Block production error"; "Error" => format!("{:?}", e)), + Ok(ValidatorEvent::SignerRejection(_slot)) => { + error!(log, "Block production error"; "Error" => format!("Signer Could not sign the block")) + } + Ok(ValidatorEvent::SlashableBlockNotProduced(_slot)) => { + error!(log, "Block production error"; "Error" => format!("Rejected the block as it could have been slashed")) + } + Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(_slot)) => { + error!(log, "Block production error"; "Error" => format!("Beacon node was unable to produce a block")) + } + } + } + /// Produce a block at some slot. /// /// Assumes that a block is required at this slot (does not check the duties). @@ -53,7 +71,7 @@ impl<'a, B: BeaconBlockNode, S: Signer> BlockProducer<'a, B, S> { /// /// The slash-protection code is not yet implemented. There is zero protection against /// slashing. - fn produce_block(&mut self) -> Result { + pub fn produce_block(&mut self) -> Result { let epoch = self.slot.epoch(self.spec.slots_per_epoch); let randao_reveal = { diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 4c01d89671..621fb03a31 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -302,20 +302,23 @@ impl Service { for (signer_index, work_type) in work { if work_type.produce_block { // spawns a thread to produce a beacon block - let signers = self.duties_manager.signers.clone(); + let signers = self.duties_manager.signers.clone(); // this is an arc let fork = self.fork.clone(); let slot = self.current_slot.clone(); let spec = self.spec.clone(); let beacon_node = self.beacon_block_client.clone(); + let log = self.log.clone(); std::thread::spawn(move || { + info!(log, "Producing a block"; "Validator"=> format!("{}", signers[signer_index])); let signer = &signers[signer_index]; - let block_producer = BlockProducer { + let mut block_producer = BlockProducer { fork, slot, spec, beacon_node, signer, }; + block_producer.handle_produce_block(log); }); // TODO: Produce a beacon block in a new thread From 25d1ddfbb0c50155cab86eb42db542eee2fe076a Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 16:34:43 +1100 Subject: [PATCH 22/57] Renames BeaconBlockNode to BeaconNodeBlock for future consistency --- .../attestation_grpc_client.rs | 45 ------------------- validator_client/src/attester_service/mod.rs | 7 ++- .../src/block_producer/beacon_block_node.rs | 33 -------------- validator_client/src/block_producer/grpc.rs | 21 ++++----- validator_client/src/block_producer/mod.rs | 16 +++---- validator_client/src/service.rs | 2 +- 6 files changed, 21 insertions(+), 103 deletions(-) delete mode 100644 validator_client/src/attester_service/attestation_grpc_client.rs delete mode 100644 validator_client/src/block_producer/beacon_block_node.rs diff --git a/validator_client/src/attester_service/attestation_grpc_client.rs b/validator_client/src/attester_service/attestation_grpc_client.rs deleted file mode 100644 index 502e51cac9..0000000000 --- a/validator_client/src/attester_service/attestation_grpc_client.rs +++ /dev/null @@ -1,45 +0,0 @@ -use protos::services_grpc::AttestationServiceClient; -use std::sync::Arc; - -use attester::{BeaconNode, BeaconNodeError, PublishOutcome}; -use protos::services::ProduceAttestationDataRequest; -use types::{Attestation, AttestationData, Slot}; - -pub struct AttestationGrpcClient { - client: Arc, -} - -impl AttestationGrpcClient { - pub fn new(client: Arc) -> Self { - Self { client } - } -} -/* -impl BeaconNode for AttestationGrpcClient { - fn produce_attestation_data( - &self, - slot: Slot, - shard: u64, - ) -> Result, BeaconNodeError> { - let mut req = ProduceAttestationDataRequest::new(); - req.set_slot(slot.as_u64()); - req.set_shard(shard); - - let reply = self - .client - .produce_attestation_data(&req) - .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; - - // TODO: return correct Attestation - Err(BeaconNodeError::DecodeFailure) - } - - fn publish_attestation( - &self, - attestation: Attestation, - ) -> Result { - // TODO: return correct PublishOutcome - Err(BeaconNodeError::DecodeFailure) - } -} -*/ diff --git a/validator_client/src/attester_service/mod.rs b/validator_client/src/attester_service/mod.rs index c146694458..1695ec0fb6 100644 --- a/validator_client/src/attester_service/mod.rs +++ b/validator_client/src/attester_service/mod.rs @@ -1,4 +1,5 @@ -mod attestation_grpc_client; +mod grpc; +/* use attester::{Attester, BeaconNode, DutiesReader, PollOutcome as AttesterPollOutcome, Signer}; use slog::{error, info, warn, Logger}; use slot_clock::SlotClock; @@ -6,10 +7,8 @@ use std::time::Duration; pub use self::attestation_grpc_client::AttestationGrpcClient; -pub struct AttesterService {} -/* pub struct AttesterService { - // pub attester: Attester, + pub attester: Attester, pub poll_interval_millis: u64, pub log: Logger, } diff --git a/validator_client/src/block_producer/beacon_block_node.rs b/validator_client/src/block_producer/beacon_block_node.rs deleted file mode 100644 index 5a581a4a7b..0000000000 --- a/validator_client/src/block_producer/beacon_block_node.rs +++ /dev/null @@ -1,33 +0,0 @@ -use types::{BeaconBlock, Signature, Slot}; -#[derive(Debug, PartialEq, Clone)] -pub enum BeaconBlockNodeError { - RemoteFailure(String), - DecodeFailure, -} - -#[derive(Debug, PartialEq, Clone)] -pub enum PublishOutcome { - ValidBlock, - InvalidBlock(String), -} - -/// Defines the methods required to produce and publish blocks on a Beacon Node. Abstracts the -/// actual beacon node. -pub trait BeaconBlockNode: Send + Sync { - /// Request that the node produces a block. - /// - /// Returns Ok(None) if the Beacon Node is unable to produce at the given slot. - fn produce_beacon_block( - &self, - slot: Slot, - randao_reveal: &Signature, - ) -> Result, BeaconBlockNodeError>; - - /// Request that the node publishes a block. - /// - /// Returns `true` if the publish was successful. - fn publish_beacon_block( - &self, - block: BeaconBlock, - ) -> Result; -} diff --git a/validator_client/src/block_producer/grpc.rs b/validator_client/src/block_producer/grpc.rs index 23477ece14..ab0d5d4210 100644 --- a/validator_client/src/block_producer/grpc.rs +++ b/validator_client/src/block_producer/grpc.rs @@ -1,4 +1,4 @@ -use super::beacon_block_node::*; +use super::beacon_node_block::*; use protos::services::{ BeaconBlock as GrpcBeaconBlock, ProduceBeaconBlockRequest, PublishBeaconBlockRequest, }; @@ -19,7 +19,7 @@ impl BeaconBlockGrpcClient { } } -impl BeaconBlockNode for BeaconBlockGrpcClient { +impl BeaconNodeBlock for BeaconBlockGrpcClient { /// Request a Beacon Node (BN) to produce a new block at the supplied slot. /// /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the @@ -28,7 +28,7 @@ impl BeaconBlockNode for BeaconBlockGrpcClient { &self, slot: Slot, randao_reveal: &Signature, - ) -> Result, BeaconBlockNodeError> { + ) -> Result, BeaconNodeError> { // request a beacon block from the node let mut req = ProduceBeaconBlockRequest::new(); req.set_slot(slot.as_u64()); @@ -38,15 +38,15 @@ impl BeaconBlockNode for BeaconBlockGrpcClient { let reply = self .client .produce_beacon_block(&req) - .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; // format the reply if reply.has_block() { let block = reply.get_block(); let ssz = block.get_ssz(); - let (block, _i) = BeaconBlock::ssz_decode(&ssz, 0) - .map_err(|_| BeaconBlockNodeError::DecodeFailure)?; + let (block, _i) = + BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| BeaconNodeError::DecodeFailure)?; Ok(Some(block)) } else { @@ -58,10 +58,7 @@ impl BeaconBlockNode for BeaconBlockGrpcClient { /// /// Generally, this will be called after a `produce_beacon_block` call with a block that has /// been completed (signed) by the validator client. - fn publish_beacon_block( - &self, - block: BeaconBlock, - ) -> Result { + fn publish_beacon_block(&self, block: BeaconBlock) -> Result { let mut req = PublishBeaconBlockRequest::new(); let ssz = ssz_encode(&block); @@ -74,10 +71,10 @@ impl BeaconBlockNode for BeaconBlockGrpcClient { let reply = self .client .publish_beacon_block(&req) - .map_err(|err| BeaconBlockNodeError::RemoteFailure(format!("{:?}", err)))?; + .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; if reply.get_success() { - Ok(PublishOutcome::ValidBlock) + Ok(PublishOutcome::Valid) } else { // TODO: distinguish between different errors Ok(PublishOutcome::InvalidBlock("Publish failed".to_string())) diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index 7def97e03c..09ce9bffae 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -1,7 +1,7 @@ -mod beacon_block_node; +mod beacon_node_block; mod grpc; -use self::beacon_block_node::{BeaconBlockNode, BeaconBlockNodeError}; +use self::beacon_node_block::{BeaconNodeBlock, BeaconNodeError}; pub use self::grpc::BeaconBlockGrpcClient; use crate::signer::Signer; use slog::{error, info}; @@ -11,7 +11,7 @@ use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; #[derive(Debug, PartialEq)] pub enum Error { - BeaconBlockNodeError(BeaconBlockNodeError), + BeaconNodeError(BeaconNodeError), } #[derive(Debug, PartialEq)] @@ -28,7 +28,7 @@ pub enum ValidatorEvent { /// This struct contains the logic for requesting and signing beacon blocks for a validator. The /// validator can abstractly sign via the Signer trait object. -pub struct BlockProducer<'a, B: BeaconBlockNode, S: Signer> { +pub struct BlockProducer<'a, B: BeaconNodeBlock, S: Signer> { /// The current fork. pub fork: Fork, /// The current slot to produce a block for. @@ -41,7 +41,7 @@ pub struct BlockProducer<'a, B: BeaconBlockNode, S: Signer> { pub signer: &'a S, } -impl<'a, B: BeaconBlockNode, S: Signer> BlockProducer<'a, B, S> { +impl<'a, B: BeaconNodeBlock, S: Signer> BlockProducer<'a, B, S> { /// Handle outputs and results from block production. pub fn handle_produce_block(&mut self, log: slog::Logger) { match self.produce_block() { @@ -147,9 +147,9 @@ impl<'a, B: BeaconBlockNode, S: Signer> BlockProducer<'a, B, S> { } } -impl From for Error { - fn from(e: BeaconBlockNodeError) -> Error { - Error::BeaconBlockNodeError(e) +impl From for Error { + fn from(e: BeaconNodeError) -> Error { + Error::BeaconNodeError(e) } } diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 621fb03a31..fd24744ace 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -8,7 +8,7 @@ /// When a validator needs to either produce a block or sign an attestation, it requests the /// data from the beacon node and performs the signing before publishing the block to the beacon /// node. -use crate::attester_service::{AttestationGrpcClient, AttesterService}; +//use crate::attester_service::{AttestationGrpcClient, AttesterService}; use crate::block_producer::{BeaconBlockGrpcClient, BlockProducer}; use crate::config::Config as ValidatorConfig; use crate::duties::{BeaconNodeDuties, DutiesManager, EpochDutiesMap, UpdateOutcome}; From c107ebf9aa353f74559643d32295584eb43662fd Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 17:06:43 +1100 Subject: [PATCH 23/57] Initial implementation of AttestationProducer --- .../beacon_node_attestation.rs | 22 +++ validator_client/src/attester_service/grpc.rs | 45 +++++ validator_client/src/attester_service/mod.rs | 172 +++++++++++++----- .../src/block_producer/beacon_node_block.rs | 31 ++++ validator_client/src/signer.rs | 15 +- 5 files changed, 222 insertions(+), 63 deletions(-) create mode 100644 validator_client/src/attester_service/beacon_node_attestation.rs create mode 100644 validator_client/src/attester_service/grpc.rs create mode 100644 validator_client/src/block_producer/beacon_node_block.rs diff --git a/validator_client/src/attester_service/beacon_node_attestation.rs b/validator_client/src/attester_service/beacon_node_attestation.rs new file mode 100644 index 0000000000..fa8d540319 --- /dev/null +++ b/validator_client/src/attester_service/beacon_node_attestation.rs @@ -0,0 +1,22 @@ +//TODO: generalise these enums to the crate +use super::block_producer::{BeaconNodeError, PublishOutcome}; + +/// Defines the methods required to produce and publish attestations on a Beacon Node. Abstracts the +/// actual beacon node. +pub trait BeaconNodeAttestation: Send + Sync { + /// Request that the node produces the required attestation data. + /// + fn produce_attestation_data( + &self, + slot: Slot, + shard: u64, + ) -> Result; + + /// Request that the node publishes a attestation. + /// + /// Returns `true` if the publish was successful. + fn publish_attestation( + &self, + attestation: Attestation, + ) -> Result; +} diff --git a/validator_client/src/attester_service/grpc.rs b/validator_client/src/attester_service/grpc.rs new file mode 100644 index 0000000000..502e51cac9 --- /dev/null +++ b/validator_client/src/attester_service/grpc.rs @@ -0,0 +1,45 @@ +use protos::services_grpc::AttestationServiceClient; +use std::sync::Arc; + +use attester::{BeaconNode, BeaconNodeError, PublishOutcome}; +use protos::services::ProduceAttestationDataRequest; +use types::{Attestation, AttestationData, Slot}; + +pub struct AttestationGrpcClient { + client: Arc, +} + +impl AttestationGrpcClient { + pub fn new(client: Arc) -> Self { + Self { client } + } +} +/* +impl BeaconNode for AttestationGrpcClient { + fn produce_attestation_data( + &self, + slot: Slot, + shard: u64, + ) -> Result, BeaconNodeError> { + let mut req = ProduceAttestationDataRequest::new(); + req.set_slot(slot.as_u64()); + req.set_shard(shard); + + let reply = self + .client + .produce_attestation_data(&req) + .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + + // TODO: return correct Attestation + Err(BeaconNodeError::DecodeFailure) + } + + fn publish_attestation( + &self, + attestation: Attestation, + ) -> Result { + // TODO: return correct PublishOutcome + Err(BeaconNodeError::DecodeFailure) + } +} +*/ diff --git a/validator_client/src/attester_service/mod.rs b/validator_client/src/attester_service/mod.rs index 1695ec0fb6..7178e28d73 100644 --- a/validator_client/src/attester_service/mod.rs +++ b/validator_client/src/attester_service/mod.rs @@ -1,59 +1,131 @@ mod grpc; -/* -use attester::{Attester, BeaconNode, DutiesReader, PollOutcome as AttesterPollOutcome, Signer}; -use slog::{error, info, warn, Logger}; -use slot_clock::SlotClock; -use std::time::Duration; +mod beacon_node_attestation; -pub use self::attestation_grpc_client::AttestationGrpcClient; +use std::sync::Arc; +use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; -pub struct AttesterService { - pub attester: Attester, - pub poll_interval_millis: u64, - pub log: Logger, +#[derive(Debug, PartialEq)] +pub enum Error { + BeaconNodeError(BeaconNodeError), } +/// This struct contains the logic for requesting and signing beacon attestations for a validator. The +/// validator can abstractly sign via the Signer trait object. +pub struct AttestationProducer<'a, B: BeaconNodeAttestation, S: Signer> { + /// The current fork. + pub fork: Fork, + /// The current slot to produce an attestation for. + pub slot: Slot, + /// The current epoch. + pub spec: Arc, + /// The beacon node to connect to. + pub beacon_node: Arc, + /// The signer to sign the block. + pub signer: &'a S, +} -impl AttesterService { - /// Run a loop which polls the Attester each `poll_interval_millis` millseconds. +impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { + /// Handle outputs and results from attestation production. + pub fn handle_produce_attestation(&mut self, log: slog::Logger) { + match self.produce_attestation() { + Ok(ValidatorEvent::AttestationProduced(_slot)) => { + info!(log, "Attestation produced"; "Validator" => format!("{}", self.signer)) + } + Err(e) => error!(log, "Attestation production error"; "Error" => format!("{:?}", e)), + Ok(ValidatorEvent::SignerRejection(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Signer could not sign the attestation")) + } + Ok(ValidatorEvent::SlashableAttestationNotProduced(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Rejected the attestation as it could have been slashed")) + } + Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to produce an attestation")) + } + } + } + + /// Produce an attestation, sign it and send it back /// - /// Logs the results of the polls. - pub fn run(&mut self) { - loop { - /* We don't do the polling any more... - match self.attester.poll() { - Err(error) => { - error!(self.log, "Attester poll error"; "error" => format!("{:?}", error)) + /// Assumes that an attestation is required at this slot (does not check the duties). + /// + /// Ensures the message is not slashable. + /// + /// !!! UNSAFE !!! + /// + /// The slash-protection code is not yet implemented. There is zero protection against + /// slashing. + pub fn produce_attestation(&mut self) -> Result { + let epoch = self.slot.epoch(self.spec.slots_per_epoch); + + if let Some(attestation) = self + .beacon_node + .produce_attestation_data(self.slot, self.shard)? + { + if self.safe_to_produce(&attestation) { + let domain = self.spec.get_domain(epoch, Domain::Attestation, &self.fork); + if let Some(attestation) = self.sign_attestation(attestation, domain) { + self.beacon_node.publish_attestation(attestation)?; + Ok(ValidatorEvent::AttestationProduced(self.slot)) + } else { + Ok(ValidatorEvent::SignerRejection(self.slot)) } - Ok(AttesterPollOutcome::AttestationProduced(slot)) => { - info!(self.log, "Produced Attestation"; "slot" => slot) - } - Ok(AttesterPollOutcome::SlashableAttestationNotProduced(slot)) => { - warn!(self.log, "Slashable attestation was not produced"; "slot" => slot) - } - Ok(AttesterPollOutcome::AttestationNotRequired(slot)) => { - info!(self.log, "Attestation not required"; "slot" => slot) - } - Ok(AttesterPollOutcome::ProducerDutiesUnknown(slot)) => { - error!(self.log, "Attestation duties unknown"; "slot" => slot) - } - Ok(AttesterPollOutcome::SlotAlreadyProcessed(slot)) => { - warn!(self.log, "Attempted to re-process slot"; "slot" => slot) - } - Ok(AttesterPollOutcome::BeaconNodeUnableToProduceAttestation(slot)) => { - error!(self.log, "Beacon node unable to produce attestation"; "slot" => slot) - } - Ok(AttesterPollOutcome::SignerRejection(slot)) => { - error!(self.log, "The cryptographic signer refused to sign the attestation"; "slot" => slot) - } - Ok(AttesterPollOutcome::ValidatorIsUnknown(slot)) => { - error!(self.log, "The Beacon Node does not recognise the validator"; "slot" => slot) - } - }; - */ -println!("Legacy polling still happening..."); -std::thread::sleep(Duration::from_millis(self.poll_interval_millis)); + } else { + Ok(ValidatorEvent::SlashableAttestationNotProduced(self.slot)) + } + } else { + Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(self.slot)) + } + } + + /// Consumes an attestation, returning the attestation signed by the validators private key. + /// + /// Important: this function will not check to ensure the attestation is not slashable. This must be + /// done upstream. + fn sign_attestation(&mut self, mut attestation: Attestation, duties: AttestationDuties, domain: u64) -> Option { + self.store_produce(&attestation); + + // build the aggregate signature + let aggregate_sig = { + let message = AttestationDataAndCustodyBit { + data: attestation.clone(), + custody_bit: false, + }.hash_tree_root(); + + let sig = self.signer.sign_message(&message, domain)?; + + let mut agg_sig = AggregateSignature::new(); + agg_sig.add(&sig); + agg_sig + } + + let mut aggregation_bitfield = Bitfield::with_capacity(duties.comitee_size); + let custody_bitfield = Bitfield::with_capacity(duties.committee_size); + aggregation_bitfield.set(duties.committee_index, true); + + Attestation { + aggregation_bitfield, + data, + custody_bitfield, + aggregate_signature, + } + } + + /// Returns `true` if signing an attestation is safe (non-slashable). + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn safe_to_produce(&self, _block: &Attestation) -> bool { + //TODO: Implement slash protection + true + } + + /// Record that an attestation was produced so that slashable votes may not be made in the future. + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn store_produce(&mut self, _block: &BeaconBlock) { + // TODO: Implement slash protection + } } -} -} -*/ diff --git a/validator_client/src/block_producer/beacon_node_block.rs b/validator_client/src/block_producer/beacon_node_block.rs new file mode 100644 index 0000000000..65ccb21047 --- /dev/null +++ b/validator_client/src/block_producer/beacon_node_block.rs @@ -0,0 +1,31 @@ +use types::{BeaconBlock, Signature, Slot}; +#[derive(Debug, PartialEq, Clone)] +pub enum BeaconNodeError { + RemoteFailure(String), + DecodeFailure, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum PublishOutcome { + Valid, + InvalidBlock(String), + InvalidAttestation(String), +} + +/// Defines the methods required to produce and publish blocks on a Beacon Node. Abstracts the +/// actual beacon node. +pub trait BeaconNodeBlock: Send + Sync { + /// Request that the node produces a block. + /// + /// Returns Ok(None) if the Beacon Node is unable to produce at the given slot. + fn produce_beacon_block( + &self, + slot: Slot, + randao_reveal: &Signature, + ) -> Result, BeaconNodeError>; + + /// Request that the node publishes a block. + /// + /// Returns `true` if the publish was successful. + fn publish_beacon_block(&self, block: BeaconBlock) -> Result; +} diff --git a/validator_client/src/signer.rs b/validator_client/src/signer.rs index 4bbada08ee..018142322f 100644 --- a/validator_client/src/signer.rs +++ b/validator_client/src/signer.rs @@ -3,8 +3,7 @@ use types::{Keypair, PublicKey, Signature}; /// Signs message using an internally-maintained private key. pub trait Signer: Display + Send + Sync + Clone { - fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option; - fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option; + fn sign_message(&self, message: &[u8], domain: u64) -> Option; /// Returns a public key for the signer object. fn to_public(&self) -> PublicKey; } @@ -16,17 +15,7 @@ impl Signer for Keypair { self.pk.clone() } - fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option { + fn sign_message(&self, message: &[u8], domain: u64) -> Option { Some(Signature::new(message, domain, &self.sk)) } - - fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option { - Some(Signature::new(message, domain, &self.sk)) - } - - /* - fn sign_attestation_message(&self, message: &[u8], domain: u64) -> Option { - Some(Signature::new(message, domain, &self.sk)) - } - */ } From bb8938c564d77a1ca5e8d18958f3ce6d9a75f7ab Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 17:14:38 +1100 Subject: [PATCH 24/57] Use AttestationDuty in epoch duties --- eth2/types/src/attestation_duty.rs | 1 + validator_client/src/duties/epoch_duties.rs | 12 +++--------- validator_client/src/duties/grpc.rs | 12 +++++++++--- validator_client/src/duties/mod.rs | 1 - 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/eth2/types/src/attestation_duty.rs b/eth2/types/src/attestation_duty.rs index 80d912a83f..9612dd868c 100644 --- a/eth2/types/src/attestation_duty.rs +++ b/eth2/types/src/attestation_duty.rs @@ -6,4 +6,5 @@ pub struct AttestationDuty { pub slot: Slot, pub shard: Shard, pub committee_index: usize, + pub comittee_size: usize, } diff --git a/validator_client/src/duties/epoch_duties.rs b/validator_client/src/duties/epoch_duties.rs index 0a4f73f723..d0753a2a6a 100644 --- a/validator_client/src/duties/epoch_duties.rs +++ b/validator_client/src/duties/epoch_duties.rs @@ -23,9 +23,7 @@ pub struct WorkInfo { #[derive(Debug, PartialEq, Clone, Copy, Default)] pub struct EpochDuty { pub block_production_slot: Option, - pub attestation_slot: Slot, - pub attestation_shard: u64, - pub committee_index: u64, + pub attestation_duty: AttestationDuty, } impl EpochDuty { @@ -39,12 +37,8 @@ impl EpochDuty { // if the validator is required to attest to a shard, create the data let mut attestation_duty = None; - if self.attestation_slot == slot { - attestation_duty = Some(AttestationDuty { - slot, - shard: self.attestation_shard, - committee_index: self.committee_index as usize, - }); + if self.attestation_duty.slot == slot { + attestation_duty = self.attestation_duty } if produce_block | attestation_duty.is_some() { diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index 66bf368a21..f0d892e98f 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -47,11 +47,17 @@ impl BeaconNodeDuties for ValidatorServiceClient { None } }; + + let attestation_duty = AttestationDuty { + slot: Slot::from(active_duty.get_attestation_slot()), + shard: active_duty.get_attestation_shard(), + committee_index: active_duty.get_committee_index(), + comittee_size: 10, + } + let epoch_duty = EpochDuty { block_production_slot, - attestation_slot: Slot::from(active_duty.get_attestation_slot()), - attestation_shard: active_duty.get_attestation_shard(), - committee_index: active_duty.get_committee_index(), + attestation_duty, }; epoch_duties.insert(pub_keys[index].clone(), Some(epoch_duty)); } diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index 9f0998567f..d52cc32544 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -30,7 +30,6 @@ pub enum UpdateOutcome { #[derive(Debug, PartialEq)] pub enum Error { DutiesMapPoisoned, - EpochMapPoisoned, BeaconNodeDutiesError(BeaconNodeDutiesError), UnknownEpoch, UnknownValidator, From 145cabc427da8fe460375aa11e37e988d868802e Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 17:56:43 +1100 Subject: [PATCH 25/57] Build validator client AttestationProducer --- .../beacon_node_attestation.rs | 3 +- validator_client/src/attester_service/mod.rs | 95 ++++++++++++------- validator_client/src/block_producer/mod.rs | 21 ++-- validator_client/src/duties/epoch_duties.rs | 4 +- validator_client/src/duties/grpc.rs | 12 +-- 5 files changed, 83 insertions(+), 52 deletions(-) diff --git a/validator_client/src/attester_service/beacon_node_attestation.rs b/validator_client/src/attester_service/beacon_node_attestation.rs index fa8d540319..b5ff777de8 100644 --- a/validator_client/src/attester_service/beacon_node_attestation.rs +++ b/validator_client/src/attester_service/beacon_node_attestation.rs @@ -1,5 +1,6 @@ //TODO: generalise these enums to the crate -use super::block_producer::{BeaconNodeError, PublishOutcome}; +use crate::block_producer::{BeaconNodeError, PublishOutcome}; +use types::{Attestation, AttestationData, Slot}; /// Defines the methods required to produce and publish attestations on a Beacon Node. Abstracts the /// actual beacon node. diff --git a/validator_client/src/attester_service/mod.rs b/validator_client/src/attester_service/mod.rs index 20488b1c19..7b2174b0cd 100644 --- a/validator_client/src/attester_service/mod.rs +++ b/validator_client/src/attester_service/mod.rs @@ -1,22 +1,38 @@ -mod grpc; mod beacon_node_attestation; +mod grpc; use std::sync::Arc; use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; -use super::block_proposer::beacon_node_block::BeaconNodeError; +//TODO: Move these higher up in the crate +use super::block_producer::{BeaconNodeError, ValidatorEvent}; +use crate::signer::Signer; +use beacon_node_attestation::BeaconNodeAttestation; +use slog::{error, info, warn}; +use ssz::TreeHash; +use types::{ + AggregateSignature, Attestation, AttestationData, AttestationDataAndCustodyBit, + AttestationDuty, Bitfield, +}; +//TODO: Group these errors at a crate level #[derive(Debug, PartialEq)] pub enum Error { BeaconNodeError(BeaconNodeError), } +impl From for Error { + fn from(e: BeaconNodeError) -> Error { + Error::BeaconNodeError(e) + } +} + /// This struct contains the logic for requesting and signing beacon attestations for a validator. The /// validator can abstractly sign via the Signer trait object. pub struct AttestationProducer<'a, B: BeaconNodeAttestation, S: Signer> { /// The current fork. pub fork: Fork, - /// The current slot to produce an attestation for. - pub slot: Slot, + /// The attestation duty to perform. + pub duty: AttestationDuty, /// The current epoch. pub spec: Arc, /// The beacon node to connect to. @@ -42,6 +58,9 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(_slot)) => { error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to produce an attestation")) } + Ok(v) => { + warn!(log, "Unknown result for attestation production"; "Error" => format!("{:?}",v)) + } } } @@ -56,25 +75,23 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { /// The slash-protection code is not yet implemented. There is zero protection against /// slashing. pub fn produce_attestation(&mut self) -> Result { - let epoch = self.slot.epoch(self.spec.slots_per_epoch); + let epoch = self.duty.slot.epoch(self.spec.slots_per_epoch); - if let Some(attestation) = self + let attestation = self .beacon_node - .produce_attestation_data(self.slot, self.shard)? - { - if self.safe_to_produce(&attestation) { - let domain = self.spec.get_domain(epoch, Domain::Attestation, &self.fork); - if let Some(attestation) = self.sign_attestation(attestation, domain) { - self.beacon_node.publish_attestation(attestation)?; - Ok(ValidatorEvent::AttestationProduced(self.slot)) - } else { - Ok(ValidatorEvent::SignerRejection(self.slot)) - } + .produce_attestation_data(self.duty.slot, self.duty.shard)?; + if self.safe_to_produce(&attestation) { + let domain = self.spec.get_domain(epoch, Domain::Attestation, &self.fork); + if let Some(attestation) = self.sign_attestation(attestation, self.duty, domain) { + self.beacon_node.publish_attestation(attestation)?; + Ok(ValidatorEvent::AttestationProduced(self.duty.slot)) } else { - Ok(ValidatorEvent::SlashableAttestationNotProduced(self.slot)) + Ok(ValidatorEvent::SignerRejection(self.duty.slot)) } } else { - Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(self.slot)) + Ok(ValidatorEvent::SlashableAttestationNotProduced( + self.duty.slot, + )) } } @@ -82,33 +99,39 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { /// /// Important: this function will not check to ensure the attestation is not slashable. This must be /// done upstream. - fn sign_attestation(&mut self, mut attestation: Attestation, duties: AttestationDuties, domain: u64) -> Option { + fn sign_attestation( + &mut self, + mut attestation: AttestationData, + duties: AttestationDuty, + domain: u64, + ) -> Option { self.store_produce(&attestation); // build the aggregate signature - let aggregate_sig = { + let aggregate_signature = { let message = AttestationDataAndCustodyBit { - data: attestation.clone(), - custody_bit: false, - }.hash_tree_root(); + data: attestation.clone(), + custody_bit: false, + } + .hash_tree_root(); let sig = self.signer.sign_message(&message, domain)?; let mut agg_sig = AggregateSignature::new(); agg_sig.add(&sig); agg_sig - } + }; - let mut aggregation_bitfield = Bitfield::with_capacity(duties.comitee_size); - let custody_bitfield = Bitfield::with_capacity(duties.committee_size); - aggregation_bitfield.set(duties.committee_index, true); + let mut aggregation_bitfield = Bitfield::with_capacity(duties.committee_len); + let custody_bitfield = Bitfield::with_capacity(duties.committee_len); + aggregation_bitfield.set(duties.committee_index, true); - Attestation { - aggregation_bitfield, - data, - custody_bitfield, - aggregate_signature, - } + Some(Attestation { + aggregation_bitfield, + data: attestation, + custody_bitfield, + aggregate_signature, + }) } /// Returns `true` if signing an attestation is safe (non-slashable). @@ -116,8 +139,8 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { /// !!! UNSAFE !!! /// /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. - fn safe_to_produce(&self, _block: &Attestation) -> bool { - //TODO: Implement slash protection + fn safe_to_produce(&self, _attestation: &AttestationData) -> bool { + //TODO: Implement slash protection true } @@ -126,7 +149,7 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { /// !!! UNSAFE !!! /// /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. - fn store_produce(&mut self, _block: &BeaconBlock) { + fn store_produce(&mut self, _attestation: &AttestationData) { // TODO: Implement slash protection } } diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index 09ce9bffae..592c0b9192 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -1,10 +1,11 @@ mod beacon_node_block; mod grpc; -use self::beacon_node_block::{BeaconNodeBlock, BeaconNodeError}; +use self::beacon_node_block::BeaconNodeBlock; +pub use self::beacon_node_block::{BeaconNodeError, PublishOutcome}; pub use self::grpc::BeaconBlockGrpcClient; use crate::signer::Signer; -use slog::{error, info}; +use slog::{error, info, warn}; use ssz::{SignedRoot, TreeHash}; use std::sync::Arc; use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; @@ -18,10 +19,16 @@ pub enum Error { pub enum ValidatorEvent { /// A new block was produced. BlockProduced(Slot), + /// A new attestation was produced. + AttestationProduced(Slot), /// A block was not produced as it would have been slashable. SlashableBlockNotProduced(Slot), + /// An attestation was not produced as it would have been slashable. + SlashableAttestationNotProduced(Slot), /// The Beacon Node was unable to produce a block at that slot. BeaconNodeUnableToProduceBlock(Slot), + /// The Beacon Node was unable to produce an attestation at that slot. + BeaconNodeUnableToProduceAttestation(Slot), /// The signer failed to sign the message. SignerRejection(Slot), } @@ -58,6 +65,9 @@ impl<'a, B: BeaconNodeBlock, S: Signer> BlockProducer<'a, B, S> { Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(_slot)) => { error!(log, "Block production error"; "Error" => format!("Beacon node was unable to produce a block")) } + Ok(v) => { + warn!(log, "Unknown result for block production"; "Error" => format!("{:?}",v)) + } } } @@ -76,7 +86,7 @@ impl<'a, B: BeaconNodeBlock, S: Signer> BlockProducer<'a, B, S> { let randao_reveal = { let message = epoch.hash_tree_root(); - let randao_reveal = match self.signer.sign_randao_reveal( + let randao_reveal = match self.signer.sign_message( &message, self.spec.get_domain(epoch, Domain::Randao, &self.fork), ) { @@ -113,10 +123,7 @@ impl<'a, B: BeaconNodeBlock, S: Signer> BlockProducer<'a, B, S> { fn sign_block(&mut self, mut block: BeaconBlock, domain: u64) -> Option { self.store_produce(&block); - match self - .signer - .sign_block_proposal(&block.signed_root()[..], domain) - { + match self.signer.sign_message(&block.signed_root()[..], domain) { None => None, Some(signature) => { block.signature = signature; diff --git a/validator_client/src/duties/epoch_duties.rs b/validator_client/src/duties/epoch_duties.rs index d0753a2a6a..692a8d6a62 100644 --- a/validator_client/src/duties/epoch_duties.rs +++ b/validator_client/src/duties/epoch_duties.rs @@ -38,7 +38,7 @@ impl EpochDuty { // if the validator is required to attest to a shard, create the data let mut attestation_duty = None; if self.attestation_duty.slot == slot { - attestation_duty = self.attestation_duty + attestation_duty = Some(self.attestation_duty) } if produce_block | attestation_duty.is_some() { @@ -60,7 +60,7 @@ impl fmt::Display for EpochDuty { write!( f, "produce block slot: {}, attestation slot: {}, attestation shard: {}", - display_block, self.attestation_slot, self.attestation_shard + display_block, self.attestation_duty.slot, self.attestation_duty.shard ) } } diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index f0d892e98f..ab87b602e0 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -6,7 +6,7 @@ use protos::services_grpc::ValidatorServiceClient; use ssz::ssz_encode; use std::collections::HashMap; use std::time::Duration; -use types::{Epoch, PublicKey, Slot}; +use types::{AttestationDuty, Epoch, PublicKey, Slot}; impl BeaconNodeDuties for ValidatorServiceClient { /// Requests all duties (block signing and committee attesting) from the Beacon Node (BN). @@ -49,11 +49,11 @@ impl BeaconNodeDuties for ValidatorServiceClient { }; let attestation_duty = AttestationDuty { - slot: Slot::from(active_duty.get_attestation_slot()), - shard: active_duty.get_attestation_shard(), - committee_index: active_duty.get_committee_index(), - comittee_size: 10, - } + slot: Slot::from(active_duty.get_attestation_slot()), + shard: active_duty.get_attestation_shard(), + committee_index: active_duty.get_committee_index() as usize, + committee_len: 10, + }; let epoch_duty = EpochDuty { block_production_slot, From d12ddae24763683eac3f9844719de8bcbc9e5ecb Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 18:14:04 +1100 Subject: [PATCH 26/57] Builds attestation grpc implemention --- protos/src/services.proto | 28 +--- .../beacon_node_attestation.rs | 23 --- validator_client/src/attester_service/grpc.rs | 45 ----- validator_client/src/attester_service/mod.rs | 155 ------------------ validator_client/src/block_producer/grpc.rs | 1 + validator_client/src/main.rs | 2 +- 6 files changed, 8 insertions(+), 246 deletions(-) delete mode 100644 validator_client/src/attester_service/beacon_node_attestation.rs delete mode 100644 validator_client/src/attester_service/grpc.rs delete mode 100644 validator_client/src/attester_service/mod.rs diff --git a/protos/src/services.proto b/protos/src/services.proto index 7cd653a600..c5bd8a91d3 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -141,7 +141,11 @@ message ProduceAttestationDataResponse { } message PublishAttestationRequest { - FreeAttestation free_attestation = 1; + Attestation attestation = 1; +} + +message Attestation { + bytes ssz = 1; } message PublishAttestationResponse { @@ -149,26 +153,6 @@ message PublishAttestationResponse { bytes msg = 2; } -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 data = 1; - bytes signature = 2; - uint64 validator_index = 3; + bytes ssz = 1; } diff --git a/validator_client/src/attester_service/beacon_node_attestation.rs b/validator_client/src/attester_service/beacon_node_attestation.rs deleted file mode 100644 index b5ff777de8..0000000000 --- a/validator_client/src/attester_service/beacon_node_attestation.rs +++ /dev/null @@ -1,23 +0,0 @@ -//TODO: generalise these enums to the crate -use crate::block_producer::{BeaconNodeError, PublishOutcome}; -use types::{Attestation, AttestationData, Slot}; - -/// Defines the methods required to produce and publish attestations on a Beacon Node. Abstracts the -/// actual beacon node. -pub trait BeaconNodeAttestation: Send + Sync { - /// Request that the node produces the required attestation data. - /// - fn produce_attestation_data( - &self, - slot: Slot, - shard: u64, - ) -> Result; - - /// Request that the node publishes a attestation. - /// - /// Returns `true` if the publish was successful. - fn publish_attestation( - &self, - attestation: Attestation, - ) -> Result; -} diff --git a/validator_client/src/attester_service/grpc.rs b/validator_client/src/attester_service/grpc.rs deleted file mode 100644 index 502e51cac9..0000000000 --- a/validator_client/src/attester_service/grpc.rs +++ /dev/null @@ -1,45 +0,0 @@ -use protos::services_grpc::AttestationServiceClient; -use std::sync::Arc; - -use attester::{BeaconNode, BeaconNodeError, PublishOutcome}; -use protos::services::ProduceAttestationDataRequest; -use types::{Attestation, AttestationData, Slot}; - -pub struct AttestationGrpcClient { - client: Arc, -} - -impl AttestationGrpcClient { - pub fn new(client: Arc) -> Self { - Self { client } - } -} -/* -impl BeaconNode for AttestationGrpcClient { - fn produce_attestation_data( - &self, - slot: Slot, - shard: u64, - ) -> Result, BeaconNodeError> { - let mut req = ProduceAttestationDataRequest::new(); - req.set_slot(slot.as_u64()); - req.set_shard(shard); - - let reply = self - .client - .produce_attestation_data(&req) - .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; - - // TODO: return correct Attestation - Err(BeaconNodeError::DecodeFailure) - } - - fn publish_attestation( - &self, - attestation: Attestation, - ) -> Result { - // TODO: return correct PublishOutcome - Err(BeaconNodeError::DecodeFailure) - } -} -*/ diff --git a/validator_client/src/attester_service/mod.rs b/validator_client/src/attester_service/mod.rs deleted file mode 100644 index 7b2174b0cd..0000000000 --- a/validator_client/src/attester_service/mod.rs +++ /dev/null @@ -1,155 +0,0 @@ -mod beacon_node_attestation; -mod grpc; - -use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; -//TODO: Move these higher up in the crate -use super::block_producer::{BeaconNodeError, ValidatorEvent}; -use crate::signer::Signer; -use beacon_node_attestation::BeaconNodeAttestation; -use slog::{error, info, warn}; -use ssz::TreeHash; -use types::{ - AggregateSignature, Attestation, AttestationData, AttestationDataAndCustodyBit, - AttestationDuty, Bitfield, -}; - -//TODO: Group these errors at a crate level -#[derive(Debug, PartialEq)] -pub enum Error { - BeaconNodeError(BeaconNodeError), -} - -impl From for Error { - fn from(e: BeaconNodeError) -> Error { - Error::BeaconNodeError(e) - } -} - -/// This struct contains the logic for requesting and signing beacon attestations for a validator. The -/// validator can abstractly sign via the Signer trait object. -pub struct AttestationProducer<'a, B: BeaconNodeAttestation, S: Signer> { - /// The current fork. - pub fork: Fork, - /// The attestation duty to perform. - pub duty: AttestationDuty, - /// The current epoch. - pub spec: Arc, - /// The beacon node to connect to. - pub beacon_node: Arc, - /// The signer to sign the block. - pub signer: &'a S, -} - -impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { - /// Handle outputs and results from attestation production. - pub fn handle_produce_attestation(&mut self, log: slog::Logger) { - match self.produce_attestation() { - Ok(ValidatorEvent::AttestationProduced(_slot)) => { - info!(log, "Attestation produced"; "Validator" => format!("{}", self.signer)) - } - Err(e) => error!(log, "Attestation production error"; "Error" => format!("{:?}", e)), - Ok(ValidatorEvent::SignerRejection(_slot)) => { - error!(log, "Attestation production error"; "Error" => format!("Signer could not sign the attestation")) - } - Ok(ValidatorEvent::SlashableAttestationNotProduced(_slot)) => { - error!(log, "Attestation production error"; "Error" => format!("Rejected the attestation as it could have been slashed")) - } - Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(_slot)) => { - error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to produce an attestation")) - } - Ok(v) => { - warn!(log, "Unknown result for attestation production"; "Error" => format!("{:?}",v)) - } - } - } - - /// Produce an attestation, sign it and send it back - /// - /// Assumes that an attestation is required at this slot (does not check the duties). - /// - /// Ensures the message is not slashable. - /// - /// !!! UNSAFE !!! - /// - /// The slash-protection code is not yet implemented. There is zero protection against - /// slashing. - pub fn produce_attestation(&mut self) -> Result { - let epoch = self.duty.slot.epoch(self.spec.slots_per_epoch); - - let attestation = self - .beacon_node - .produce_attestation_data(self.duty.slot, self.duty.shard)?; - if self.safe_to_produce(&attestation) { - let domain = self.spec.get_domain(epoch, Domain::Attestation, &self.fork); - if let Some(attestation) = self.sign_attestation(attestation, self.duty, domain) { - self.beacon_node.publish_attestation(attestation)?; - Ok(ValidatorEvent::AttestationProduced(self.duty.slot)) - } else { - Ok(ValidatorEvent::SignerRejection(self.duty.slot)) - } - } else { - Ok(ValidatorEvent::SlashableAttestationNotProduced( - self.duty.slot, - )) - } - } - - /// Consumes an attestation, returning the attestation signed by the validators private key. - /// - /// Important: this function will not check to ensure the attestation is not slashable. This must be - /// done upstream. - fn sign_attestation( - &mut self, - mut attestation: AttestationData, - duties: AttestationDuty, - domain: u64, - ) -> Option { - self.store_produce(&attestation); - - // build the aggregate signature - let aggregate_signature = { - let message = AttestationDataAndCustodyBit { - data: attestation.clone(), - custody_bit: false, - } - .hash_tree_root(); - - let sig = self.signer.sign_message(&message, domain)?; - - let mut agg_sig = AggregateSignature::new(); - agg_sig.add(&sig); - agg_sig - }; - - let mut aggregation_bitfield = Bitfield::with_capacity(duties.committee_len); - let custody_bitfield = Bitfield::with_capacity(duties.committee_len); - aggregation_bitfield.set(duties.committee_index, true); - - Some(Attestation { - aggregation_bitfield, - data: attestation, - custody_bitfield, - aggregate_signature, - }) - } - - /// Returns `true` if signing an attestation is safe (non-slashable). - /// - /// !!! UNSAFE !!! - /// - /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. - fn safe_to_produce(&self, _attestation: &AttestationData) -> bool { - //TODO: Implement slash protection - true - } - - /// Record that an attestation was produced so that slashable votes may not be made in the future. - /// - /// !!! UNSAFE !!! - /// - /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. - fn store_produce(&mut self, _attestation: &AttestationData) { - // TODO: Implement slash protection - } -} diff --git a/validator_client/src/block_producer/grpc.rs b/validator_client/src/block_producer/grpc.rs index ab0d5d4210..fd1a70fa2f 100644 --- a/validator_client/src/block_producer/grpc.rs +++ b/validator_client/src/block_producer/grpc.rs @@ -7,6 +7,7 @@ use ssz::{ssz_encode, Decodable}; use std::sync::Arc; use types::{BeaconBlock, Signature, Slot}; +//TODO: Remove this new type. Do not need to wrap /// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be /// implemented upon it. pub struct BeaconBlockGrpcClient { diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 4817667348..7a353e0dcc 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,4 +1,4 @@ -mod attester_service; +mod attestation_producer; mod block_producer; mod config; mod duties; From c2b6f949c0e6ab0693f5cf036078d1f7a4b16408 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 30 Mar 2019 18:25:32 +1100 Subject: [PATCH 27/57] Restrict blop pool from re-including attestations --- .../specs/validator_registry.yaml | 3 ++- eth2/operation_pool/src/lib.rs | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml index 1674ecffc7..ad9c899cfe 100644 --- a/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml +++ b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml @@ -48,7 +48,8 @@ test_cases: - slot: 63 num_validators: 1003 num_previous_epoch_attestations: 0 - num_current_epoch_attestations: 10 + # slots_per_epoch - attestation_inclusion_delay - skip_slots + num_current_epoch_attestations: 57 slashed_validators: [11, 12, 13, 14, 42] exited_validators: [] exit_initiated_validators: [50] diff --git a/eth2/operation_pool/src/lib.rs b/eth2/operation_pool/src/lib.rs index c42527b608..9d4d0091ab 100644 --- a/eth2/operation_pool/src/lib.rs +++ b/eth2/operation_pool/src/lib.rs @@ -172,6 +172,8 @@ impl OperationPool { || key.domain_bytes_match(&curr_domain_bytes) }) .flat_map(|(_, attestations)| attestations) + // That are not superseded by an attestation included in the state... + .filter(|attestation| !superior_attestation_exists_in_state(state, attestation)) // That are valid... .filter(|attestation| validate_attestation(state, attestation, spec).is_ok()) // Scored by the number of new attestations they introduce (descending) @@ -462,6 +464,31 @@ impl OperationPool { } } +/// Returns `true` if the state already contains a `PendingAttestation` that is superior to the +/// given `attestation`. +/// +/// A validator has nothing to gain from re-including an attestation and it adds load to the +/// network. +/// +/// An existing `PendingAttestation` is superior to an existing `attestation` if: +/// +/// - Their `AttestationData` is equal. +/// - `attestation` does not contain any signatures that `PendingAttestation` does not have. +fn superior_attestation_exists_in_state(state: &BeaconState, attestation: &Attestation) -> bool { + state + .current_epoch_attestations + .iter() + .chain(state.previous_epoch_attestations.iter()) + .any(|existing_attestation| { + let bitfield = &attestation.aggregation_bitfield; + let existing_bitfield = &existing_attestation.aggregation_bitfield; + + existing_attestation.data == attestation.data + && bitfield.intersection(existing_bitfield).num_set_bits() + == bitfield.num_set_bits() + }) +} + /// Filter up to a maximum number of operations out of an iterator. fn filter_limit_operations<'a, T: 'a, I, F>(operations: I, filter: F, limit: u64) -> Vec where From d8fd7c8803f3ed780177463a060fb0e3fa44a7d8 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 18:36:38 +1100 Subject: [PATCH 28/57] Implement beacon node side of attestation production gRPC --- beacon_node/rpc/src/beacon_attester.rs | 100 +++++++++++++++++++------ 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/beacon_node/rpc/src/beacon_attester.rs b/beacon_node/rpc/src/beacon_attester.rs index 4166c30d4d..d787d1e5f0 100644 --- a/beacon_node/rpc/src/beacon_attester.rs +++ b/beacon_node/rpc/src/beacon_attester.rs @@ -7,9 +7,7 @@ use protos::services::{ PublishAttestation }; use protos::services_grpc::BeaconBlockService; -use slog::{Logger, info, warn, error}; - -const TEST_SHARD_PHASE_ZERO: u8 = 0; +use slog::{Logger, info, warn, error, trace}; #[derive(Clone)] pub struct AttestationServiceInstance { @@ -25,30 +23,47 @@ impl AttestationService for AttestationServiceInstance { req: ProduceAttestationDataRequest, sink: UnarySink, ) { - info!(&self.log, "Attempting to produce attestation at slot {}", req.get_slot()); + trace!(&self.log, "Attempting to produce attestation at slot {}", req.get_slot()); - // get the chain spec & state - let spec = self.chain.get_spec(); - let state = self.chain.get_state(); + // verify the slot, drop lock on state afterwards + { + let slot_requested = req.get_slot(); + let state = self.chain.get_state(); - let slot_requested = req.get_slot(); - - // Start by performing some checks - // Check that the the AttestionData is for the current slot (otherwise it will not be valid) - if slot_requested != state.slot { - let f = sink - .fail(RpcStatus::new( - RpcStatusCode::OutOfRange, - "AttestationData request for a slot that is not the current slot." - )) - .map_err(move |e| error!(&self.log, "Failed to reply with failure {:?}: {:?}", req, e)); + // Start by performing some checks + // Check that the the AttestionData is for the current slot (otherwise it will not be valid) + if slot_requested != state.slot { + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::OutOfRange, + "AttestationData request for a slot that is not the current slot." + )) + .map_err(move |e| error!(&self.log, "Failed to reply with failure {:?}: {:?}", req, e)); + } } - // Then get the AttestationData from the beacon chain (for shard 0 for now) - let attestation_data = self.chain.produce_attestation_data(TEST_SHARD_PHASE_ZERO); + // Then get the AttestationData from the beacon chain + let attestation_data = match self.chain.produce_attestation_data(req.get_shard()){ + Ok(v) => v, + Err(e) => { + // Could not produce an attestation + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::Unknown + Some(format!("Could not produce an attestation: {:?}",e)), + )) + .map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e)); + return ctx.spawn(f); + } + }; + + + let mut attestation_data_proto = AttestationDataProto::new(); + attestation_data_proto.set_ssz(ssz_encode(&attestation_data)); let mut resp = ProduceAttestationDataResponse::new(); - resp.set_attestation_data(attestation_data); + resp.set_attestation_data(attestation_data_proto); let f = sink .success(resp) @@ -64,12 +79,49 @@ impl AttestationService for AttestationServiceInstance { req: PublishAttestationRequest, sink: UnarySink, ) { - println!("publishing attestation {:?}", req.get_block()); + trace!(self.log, "Publishing attestation"); - // TODO: actually process the block. let mut resp = PublishAttestationResponse::new(); + let ssz_serialized_attestation = req.get_attestation().get_ssz(); - resp.set_success(true); + let attestation = match Attestation::ssz_decode(ssz_serialized_attestation, 0) { + Ok((v, _index)) => v, + Err(_) => { + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::InvalidArgument, + Some("Invalid attestation".to_string()), + )) + .map_err(move |e| warn!(log_clone, "failed to reply {:?}", req)); + return ctx.spawn(f); + } + }; + + match self.chain.process_attestation(attestation) { + Ok(_) => { + // Attestation was successfully processed. + info!( + self.log, + "PublishAttestation"; + "type" => "valid_attestation", + ); + + resp.set_success(true); + }, + Err(e)=> { + // Attestation was invalid + warn!( + self.log, + "PublishAttestation"; + "type" => "invalid_attestation", + ); + resp.set_success(false); + resp.set_msg( + format!("InvalidAttestation: {:?}", e).as_bytes().to_vec(), + ); + } + }; let f = sink .success(resp) From e1befe9d3a22fe8d1c2414757ad6be0672221ae6 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 18:46:06 +1100 Subject: [PATCH 29/57] Adds attestation producer to the validation client --- validator_client/src/service.rs | 53 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index fd24744ace..ba0795ec10 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -8,15 +8,13 @@ /// When a validator needs to either produce a block or sign an attestation, it requests the /// data from the beacon node and performs the signing before publishing the block to the beacon /// node. -//use crate::attester_service::{AttestationGrpcClient, AttesterService}; +use crate::attestation_producer::AttestationProducer; use crate::block_producer::{BeaconBlockGrpcClient, BlockProducer}; use crate::config::Config as ValidatorConfig; -use crate::duties::{BeaconNodeDuties, DutiesManager, EpochDutiesMap, UpdateOutcome}; +use crate::duties::{BeaconNodeDuties, DutiesManager, EpochDutiesMap}; use crate::error as error_chain; use crate::error::ErrorKind; use crate::signer::Signer; -use attester::test_utils::EpochMap; -use attester::{test_utils::LocalSigner as AttesterLocalSigner, Attester}; use bls::Keypair; use grpcio::{ChannelBuilder, EnvBuilder}; use protos::services::Empty; @@ -24,11 +22,10 @@ use protos::services_grpc::{ AttestationServiceClient, BeaconBlockServiceClient, BeaconNodeServiceClient, ValidatorServiceClient, }; -use slog::{debug, error, info, warn}; +use slog::{error, info, warn}; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::sync::Arc; use std::sync::RwLock; -use std::thread; use std::time::{Duration, Instant, SystemTime}; use tokio::prelude::*; use tokio::runtime::Builder; @@ -55,7 +52,7 @@ pub struct Service { /// The beacon block GRPC client. beacon_block_client: Arc, /// The attester GRPC client. - attester_client: Arc, + attestation_client: Arc, /// The validator client logger. log: slog::Logger, } @@ -148,7 +145,7 @@ impl Service { }; //Beacon node gRPC attester endpoints. - let attester_client = { + let attestation_client = { let ch = ChannelBuilder::new(env.clone()).connect(&config.server); Arc::new(AttestationServiceClient::new(ch)) }; @@ -194,7 +191,7 @@ impl Service { spec, duties_manager, beacon_block_client, - attester_client, + attestation_client, log, }) } @@ -301,6 +298,7 @@ impl Service { if let Some(work) = self.duties_manager.get_current_work(self.current_slot) { for (signer_index, work_type) in work { if work_type.produce_block { + // we need to produce a block // spawns a thread to produce a beacon block let signers = self.duties_manager.signers.clone(); // this is an arc let fork = self.fork.clone(); @@ -320,26 +318,27 @@ impl Service { }; block_producer.handle_produce_block(log); }); - - // TODO: Produce a beacon block in a new thread } if work_type.attestation_duty.is_some() { - // available AttestationDuty info - /* - let attestation_duty = - work_type.attestation_duty.expect("Cannot be None"); - let attester_grpc_client = Arc::new(AttestationGrpcClient::new( - service.attester_client.clone(), - )); - let signer = Arc::new(AttesterLocalSigner::new(keypair.clone())); - let attester = Attester::new(attester_grpc_client, signer); - let mut attester_service = AttesterService { - attester, - poll_interval_millis: POLL_INTERVAL_MILLIS, - log: log.clone(), - }; - attester_service.run(); - */ + // we need to produce an attestation + // spawns a thread to produce and sign an attestation + let signers = self.duties_manager.signers.clone(); // this is an arc + let fork = self.fork.clone(); + let spec = self.spec.clone(); + let beacon_node = self.attestation_client.clone(); + let log = self.log.clone(); + std::thread::spawn(move || { + info!(log, "Producing an attestation"; "Validator"=> format!("{}", signers[signer_index])); + let signer = &signers[signer_index]; + let mut attestation_producer = AttestationProducer { + fork, + duty: work_type.attestation_duty.expect("Should never be none"), + spec, + beacon_node, + signer, + }; + attestation_producer.handle_produce_attestation(log); + }); } } } From 935c64deef97431d71e20272c9411e99ecfd2722 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 30 Mar 2019 19:11:52 +1100 Subject: [PATCH 30/57] Adds attestation validation to `SimpleSync` --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/beacon_chain/src/lib.rs | 4 ++++ beacon_node/network/src/beacon_chain.rs | 15 +++++---------- beacon_node/network/src/sync/simple_sync.rs | 7 ++----- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 7c2336a28b..6ca0bff733 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -11,7 +11,7 @@ use operation_pool::OperationPool; use parking_lot::{RwLock, RwLockReadGuard}; use slot_clock::SlotClock; use ssz::ssz_encode; -pub use state_processing::per_block_processing::errors::{ +use state_processing::per_block_processing::errors::{ AttestationValidationError, AttesterSlashingValidationError, DepositValidationError, ExitValidationError, ProposerSlashingValidationError, TransferValidationError, }; diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 48a42b941e..409b86e028 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -13,4 +13,8 @@ pub use db; pub use fork_choice; pub use parking_lot; pub use slot_clock; +pub use state_processing::per_block_processing::errors::{ + AttestationValidationError, AttesterSlashingValidationError, DepositValidationError, + ExitValidationError, ProposerSlashingValidationError, TransferValidationError, +}; pub use types; diff --git a/beacon_node/network/src/beacon_chain.rs b/beacon_node/network/src/beacon_chain.rs index 8ec8162ff7..7a8efb2540 100644 --- a/beacon_node/network/src/beacon_chain.rs +++ b/beacon_node/network/src/beacon_chain.rs @@ -5,7 +5,7 @@ use beacon_chain::{ parking_lot::RwLockReadGuard, slot_clock::SlotClock, types::{BeaconState, ChainSpec}, - AggregationOutcome, CheckPoint, + AttestationValidationError, CheckPoint, }; use eth2_libp2p::rpc::HelloMessage; use types::{Attestation, BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; @@ -40,7 +40,7 @@ pub trait BeaconChain: Send + Sync { fn process_attestation( &self, attestation: Attestation, - ) -> Result; + ) -> Result<(), AttestationValidationError>; fn get_block_roots( &self, @@ -126,14 +126,9 @@ where fn process_attestation( &self, - _attestation: Attestation, - ) -> Result { - // Awaiting a proper operations pool before we can import attestations. - // - // Returning a useless error for now. - // - // https://github.com/sigp/lighthouse/issues/281 - return Err(BeaconChainError::DBInconsistent("CANNOT PROCESS".into())); + attestation: Attestation, + ) -> Result<(), AttestationValidationError> { + self.process_attestation(attestation) } fn get_block_roots( diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 85949fa98e..1afba018d9 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -563,12 +563,9 @@ impl SimpleSync { "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) { - Ok(_) => panic!("Impossible, method not implemented."), - Err(_) => error!(self.log, "Attestation processing not implemented!"), + Ok(()) => info!(self.log, "ImportedAttestation"), + Err(e) => warn!(self.log, "InvalidAttestation"; "error" => format!("{:?}", e)), } } From 65ae8fda47cc323d7ed591037e3968b97ee978ec Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 30 Mar 2019 19:15:15 +1100 Subject: [PATCH 31/57] Remove old attestation aggregator --- .../src/attestation_aggregator.rs | 218 ------------------ beacon_node/beacon_chain/src/lib.rs | 2 - 2 files changed, 220 deletions(-) delete mode 100644 beacon_node/beacon_chain/src/attestation_aggregator.rs diff --git a/beacon_node/beacon_chain/src/attestation_aggregator.rs b/beacon_node/beacon_chain/src/attestation_aggregator.rs deleted file mode 100644 index 9b4e5a6874..0000000000 --- a/beacon_node/beacon_chain/src/attestation_aggregator.rs +++ /dev/null @@ -1,218 +0,0 @@ -use ssz::TreeHash; -use state_processing::per_block_processing::validate_attestation_without_signature; -use std::collections::{HashMap, HashSet}; -use types::*; - -const PHASE_0_CUSTODY_BIT: bool = false; - -/// Provides the functionality to: -/// -/// - Recieve a `FreeAttestation` and aggregate it into an `Attestation` (or create a new if it -/// doesn't exist). -/// - Store all aggregated or created `Attestation`s. -/// - Produce a list of attestations that would be valid for inclusion in some `BeaconState` (and -/// therefore valid for inclusion in a `BeaconBlock`. -/// -/// Note: `Attestations` are stored in memory and never deleted. This is not scalable and must be -/// rectified in a future revision. -#[derive(Default)] -pub struct AttestationAggregator { - store: HashMap, Attestation>, -} - -pub struct Outcome { - pub valid: bool, - pub message: Message, -} - -pub enum Message { - /// The free attestation was added to an existing attestation. - Aggregated, - /// The free attestation has already been aggregated to an existing attestation. - AggregationNotRequired, - /// The free attestation was transformed into a new attestation. - NewAttestationCreated, - /// The supplied `validator_index` is not in the committee for the given `shard` and `slot`. - BadValidatorIndex, - /// The given `signature` did not match the `pubkey` in the given - /// `state.validator_registry`. - BadSignature, - /// The given `slot` does not match the validators committee assignment. - BadSlot, - /// The given `shard` does not match the validators committee assignment, or is not included in - /// a committee for the given slot. - BadShard, - /// Attestation is from the epoch prior to this, ignoring. - TooOld, -} - -macro_rules! valid_outcome { - ($error: expr) => { - return Ok(Outcome { - valid: true, - message: $error, - }); - }; -} - -macro_rules! invalid_outcome { - ($error: expr) => { - return Ok(Outcome { - valid: false, - message: $error, - }); - }; -} - -impl AttestationAggregator { - /// Instantiates a new AttestationAggregator with an empty database. - pub fn new() -> Self { - Self { - store: HashMap::new(), - } - } - - /// Accepts some `FreeAttestation`, validates it and either aggregates it upon some existing - /// `Attestation` or produces a new `Attestation`. - /// - /// The "validation" provided is not complete, instead the following points are checked: - /// - The given `validator_index` is in the committee for the given `shard` for the given - /// `slot`. - /// - The signature is verified against that of the validator at `validator_index`. - pub fn process_free_attestation( - &mut self, - state: &BeaconState, - free_attestation: &FreeAttestation, - spec: &ChainSpec, - ) -> Result { - let duties = - match state.get_attestation_duties(free_attestation.validator_index as usize, spec) { - Err(BeaconStateError::EpochCacheUninitialized(e)) => { - panic!("Attempted to access unbuilt cache {:?}.", e) - } - Err(BeaconStateError::EpochOutOfBounds) => invalid_outcome!(Message::TooOld), - Err(BeaconStateError::ShardOutOfBounds) => invalid_outcome!(Message::BadShard), - Err(e) => return Err(e), - Ok(None) => invalid_outcome!(Message::BadValidatorIndex), - Ok(Some(attestation_duties)) => attestation_duties, - }; - - if free_attestation.data.slot != duties.slot { - invalid_outcome!(Message::BadSlot); - } - if free_attestation.data.shard != duties.shard { - invalid_outcome!(Message::BadShard); - } - - let signable_message = AttestationDataAndCustodyBit { - data: free_attestation.data.clone(), - custody_bit: PHASE_0_CUSTODY_BIT, - } - .hash_tree_root(); - - let validator_record = match state - .validator_registry - .get(free_attestation.validator_index as usize) - { - None => invalid_outcome!(Message::BadValidatorIndex), - Some(validator_record) => validator_record, - }; - - if !free_attestation.signature.verify( - &signable_message, - spec.get_domain(state.current_epoch(spec), Domain::Attestation, &state.fork), - &validator_record.pubkey, - ) { - invalid_outcome!(Message::BadSignature); - } - - if let Some(existing_attestation) = self.store.get(&signable_message) { - if let Some(updated_attestation) = aggregate_attestation( - existing_attestation, - &free_attestation.signature, - duties.committee_index as usize, - ) { - self.store.insert(signable_message, updated_attestation); - valid_outcome!(Message::Aggregated); - } else { - valid_outcome!(Message::AggregationNotRequired); - } - } else { - let mut aggregate_signature = AggregateSignature::new(); - aggregate_signature.add(&free_attestation.signature); - let mut aggregation_bitfield = Bitfield::new(); - aggregation_bitfield.set(duties.committee_index as usize, true); - let new_attestation = Attestation { - data: free_attestation.data.clone(), - aggregation_bitfield, - custody_bitfield: Bitfield::new(), - aggregate_signature, - }; - self.store.insert(signable_message, new_attestation); - valid_outcome!(Message::NewAttestationCreated); - } - } - - /// Returns all known attestations which are: - /// - /// - Valid for the given state - /// - Not already in `state.latest_attestations`. - pub fn get_attestations_for_state( - &self, - state: &BeaconState, - spec: &ChainSpec, - ) -> Vec { - let mut known_attestation_data: HashSet = HashSet::new(); - - state - .previous_epoch_attestations - .iter() - .chain(state.current_epoch_attestations.iter()) - .for_each(|attestation| { - known_attestation_data.insert(attestation.data.clone()); - }); - - self.store - .values() - .filter_map(|attestation| { - if validate_attestation_without_signature(&state, attestation, spec).is_ok() - && !known_attestation_data.contains(&attestation.data) - { - Some(attestation.clone()) - } else { - None - } - }) - .collect() - } -} - -/// Produces a new `Attestation` where: -/// -/// - `signature` is added to `Attestation.aggregate_signature` -/// - Attestation.aggregation_bitfield[committee_index]` is set to true. -fn aggregate_attestation( - existing_attestation: &Attestation, - signature: &Signature, - committee_index: usize, -) -> Option { - let already_signed = existing_attestation - .aggregation_bitfield - .get(committee_index) - .unwrap_or(false); - - if already_signed { - None - } else { - let mut aggregation_bitfield = existing_attestation.aggregation_bitfield.clone(); - aggregation_bitfield.set(committee_index, true); - let mut aggregate_signature = existing_attestation.aggregate_signature.clone(); - aggregate_signature.add(&signature); - - Some(Attestation { - aggregation_bitfield, - aggregate_signature, - ..existing_attestation.clone() - }) - } -} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 409b86e028..8b8fcb5e62 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,4 +1,3 @@ -mod attestation_aggregator; mod beacon_chain; mod checkpoint; mod errors; @@ -8,7 +7,6 @@ pub mod test_utils; pub use self::beacon_chain::{BeaconChain, BlockProcessingOutcome, InvalidBlock, ValidBlock}; pub use self::checkpoint::CheckPoint; pub use self::errors::BeaconChainError; -pub use attestation_aggregator::Outcome as AggregationOutcome; pub use db; pub use fork_choice; pub use parking_lot; From fc5142c09abbe11597f1ac302c13125a554e42a6 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 19:32:32 +1100 Subject: [PATCH 32/57] Registers the attester service to the beacon node RPC client --- beacon_node/beacon_chain/src/lib.rs | 4 +- beacon_node/rpc/src/attestation.rs | 143 ++++++++++++++++ beacon_node/rpc/src/beacon_attester.rs | 131 --------------- beacon_node/rpc/src/beacon_block.rs | 6 +- beacon_node/rpc/src/beacon_chain.rs | 22 ++- beacon_node/rpc/src/lib.rs | 17 +- .../testing_beacon_state_builder.rs | 2 +- .../beacon_node_attestation.rs | 23 +++ .../src/attestation_producer/grpc.rs | 59 +++++++ .../src/attestation_producer/mod.rs | 155 ++++++++++++++++++ validator_client/src/service.rs | 3 + 11 files changed, 425 insertions(+), 140 deletions(-) create mode 100644 beacon_node/rpc/src/attestation.rs delete mode 100644 beacon_node/rpc/src/beacon_attester.rs create mode 100644 validator_client/src/attestation_producer/beacon_node_attestation.rs create mode 100644 validator_client/src/attestation_producer/grpc.rs create mode 100644 validator_client/src/attestation_producer/mod.rs diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 234a960945..69ff14671b 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -5,7 +5,9 @@ mod errors; pub mod initialise; pub mod test_utils; -pub use self::beacon_chain::{BeaconChain, BlockProcessingOutcome, InvalidBlock, ValidBlock}; +pub use self::beacon_chain::{ + AttestationValidationError, BeaconChain, BlockProcessingOutcome, InvalidBlock, ValidBlock, +}; pub use self::checkpoint::CheckPoint; pub use self::errors::{BeaconChainError, BlockProductionError}; pub use attestation_aggregator::Outcome as AggregationOutcome; diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs new file mode 100644 index 0000000000..f9423df615 --- /dev/null +++ b/beacon_node/rpc/src/attestation.rs @@ -0,0 +1,143 @@ +use crate::beacon_chain::BeaconChain; +use futures::Future; +use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink}; +use protos::services::{ + AttestationData as AttestationDataProto, ProduceAttestationDataRequest, + ProduceAttestationDataResponse, PublishAttestationRequest, PublishAttestationResponse, +}; +use protos::services_grpc::AttestationService; +use slog::{error, info, trace, warn, Logger}; +use ssz::{ssz_encode, Decodable}; +use std::sync::Arc; +use types::Attestation; + +#[derive(Clone)] +pub struct AttestationServiceInstance { + pub chain: Arc, + pub log: slog::Logger, +} + +impl AttestationService for AttestationServiceInstance { + /// Produce the `AttestationData` for signing by a validator. + fn produce_attestation_data( + &mut self, + ctx: RpcContext, + req: ProduceAttestationDataRequest, + sink: UnarySink, + ) { + warn!( + &self.log, + "Attempting to produce attestation at slot {}", + req.get_slot() + ); + + // verify the slot, drop lock on state afterwards + { + let slot_requested = req.get_slot(); + let state = self.chain.get_state(); + + // Start by performing some checks + // Check that the the AttestionData is for the current slot (otherwise it will not be valid) + if slot_requested != state.slot.as_u64() { + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::OutOfRange, + Some(format!( + "AttestationData request for a slot that is not the current slot." + )), + )) + .map_err(move |e| { + error!(log_clone, "Failed to reply with failure {:?}: {:?}", req, e) + }); + return ctx.spawn(f); + } + } + + // Then get the AttestationData from the beacon chain + let shard = req.get_shard(); + let attestation_data = match self.chain.produce_attestation_data(shard) { + Ok(v) => v, + Err(e) => { + // Could not produce an attestation + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::Unknown, + Some(format!("Could not produce an attestation: {:?}", e)), + )) + .map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e)); + return ctx.spawn(f); + } + }; + + let mut attestation_data_proto = AttestationDataProto::new(); + attestation_data_proto.set_ssz(ssz_encode(&attestation_data)); + + let mut resp = ProduceAttestationDataResponse::new(); + resp.set_attestation_data(attestation_data_proto); + + let error_log = self.log.clone(); + let f = sink + .success(resp) + .map_err(move |e| error!(error_log, "Failed to reply with success {:?}: {:?}", req, e)); + ctx.spawn(f) + } + + /// Accept some fully-formed `FreeAttestation` from the validator, + /// store it, and aggregate it into an `Attestation`. + fn publish_attestation( + &mut self, + ctx: RpcContext, + req: PublishAttestationRequest, + sink: UnarySink, + ) { + warn!(self.log, "Publishing attestation"); + + let mut resp = PublishAttestationResponse::new(); + let ssz_serialized_attestation = req.get_attestation().get_ssz(); + + let attestation = match Attestation::ssz_decode(ssz_serialized_attestation, 0) { + Ok((v, _index)) => v, + Err(_) => { + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::InvalidArgument, + Some("Invalid attestation".to_string()), + )) + .map_err(move |_| warn!(log_clone, "failed to reply {:?}", req)); + return ctx.spawn(f); + } + }; + + match self.chain.process_attestation(attestation) { + Ok(_) => { + // Attestation was successfully processed. + info!( + self.log, + "PublishAttestation"; + "type" => "valid_attestation", + ); + + resp.set_success(true); + } + Err(e) => { + // Attestation was invalid + warn!( + self.log, + "PublishAttestation"; + "type" => "invalid_attestation", + ); + resp.set_success(false); + resp.set_msg(format!("InvalidAttestation: {:?}", e).as_bytes().to_vec()); + } + }; + + let error_log = self.log.clone(); + let f = sink + .success(resp) + .map_err(move |e| error!(error_log, "failed to reply {:?}: {:?}", req, e)); + ctx.spawn(f) + } +} diff --git a/beacon_node/rpc/src/beacon_attester.rs b/beacon_node/rpc/src/beacon_attester.rs deleted file mode 100644 index d787d1e5f0..0000000000 --- a/beacon_node/rpc/src/beacon_attester.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::beacon_chain::BeaconChain; -use futures::Future; -use grpcio::{RpcContext, UnarySink, RpcStatus, RpcStatusCode}; -use protos::services::{ - AttestationData as AttestationDataProto, ProduceAttestationData, ProduceAttestationDataResponse, - ProduceAttestationDataRequest, PublishAttestationResponse, PublishAttestationRequest, - PublishAttestation -}; -use protos::services_grpc::BeaconBlockService; -use slog::{Logger, info, warn, error, trace}; - -#[derive(Clone)] -pub struct AttestationServiceInstance { - pub chain: Arc, - pub log: Logger, -} - -impl AttestationService for AttestationServiceInstance { - /// Produce the `AttestationData` for signing by a validator. - fn produce_attestation_data( - &mut self, - ctx: RpcContext, - req: ProduceAttestationDataRequest, - sink: UnarySink, - ) { - trace!(&self.log, "Attempting to produce attestation at slot {}", req.get_slot()); - - // verify the slot, drop lock on state afterwards - { - let slot_requested = req.get_slot(); - let state = self.chain.get_state(); - - // Start by performing some checks - // Check that the the AttestionData is for the current slot (otherwise it will not be valid) - if slot_requested != state.slot { - let f = sink - .fail(RpcStatus::new( - RpcStatusCode::OutOfRange, - "AttestationData request for a slot that is not the current slot." - )) - .map_err(move |e| error!(&self.log, "Failed to reply with failure {:?}: {:?}", req, e)); - } - } - - // Then get the AttestationData from the beacon chain - let attestation_data = match self.chain.produce_attestation_data(req.get_shard()){ - Ok(v) => v, - Err(e) => { - // Could not produce an attestation - let log_clone = self.log.clone(); - let f = sink - .fail(RpcStatus::new( - RpcStatusCode::Unknown - Some(format!("Could not produce an attestation: {:?}",e)), - )) - .map_err(move |e| warn!(log_clone, "failed to reply {:?}: {:?}", req, e)); - return ctx.spawn(f); - } - }; - - - let mut attestation_data_proto = AttestationDataProto::new(); - attestation_data_proto.set_ssz(ssz_encode(&attestation_data)); - - let mut resp = ProduceAttestationDataResponse::new(); - resp.set_attestation_data(attestation_data_proto); - - let f = sink - .success(resp) - .map_err(move |e| error!("Failed to reply with success {:?}: {:?}", req, e)); - ctx.spawn(f) - } - - /// Accept some fully-formed `FreeAttestation` from the validator, - /// store it, and aggregate it into an `Attestation`. - fn publish_attestation( - &mut self, - ctx: RpcContext, - req: PublishAttestationRequest, - sink: UnarySink, - ) { - trace!(self.log, "Publishing attestation"); - - let mut resp = PublishAttestationResponse::new(); - let ssz_serialized_attestation = req.get_attestation().get_ssz(); - - let attestation = match Attestation::ssz_decode(ssz_serialized_attestation, 0) { - Ok((v, _index)) => v, - Err(_) => { - let log_clone = self.log.clone(); - let f = sink - .fail(RpcStatus::new( - RpcStatusCode::InvalidArgument, - Some("Invalid attestation".to_string()), - )) - .map_err(move |e| warn!(log_clone, "failed to reply {:?}", req)); - return ctx.spawn(f); - } - }; - - match self.chain.process_attestation(attestation) { - Ok(_) => { - // Attestation was successfully processed. - info!( - self.log, - "PublishAttestation"; - "type" => "valid_attestation", - ); - - resp.set_success(true); - }, - Err(e)=> { - // Attestation was invalid - warn!( - self.log, - "PublishAttestation"; - "type" => "invalid_attestation", - ); - resp.set_success(false); - resp.set_msg( - format!("InvalidAttestation: {:?}", e).as_bytes().to_vec(), - ); - } - }; - - let f = sink - .success(resp) - .map_err(move |e| println!("failed to reply {:?}: {:?}", req, e)); - ctx.spawn(f) - } -} diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index e8b3cb01b4..adb0a3c22f 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -36,8 +36,8 @@ impl BeaconBlockService for BeaconBlockServiceInstance { // decode the request // TODO: requested slot currently unused, see: https://github.com/sigp/lighthouse/issues/336 let _requested_slot = Slot::from(req.get_slot()); - let (randao_reveal, _index) = match Signature::ssz_decode(req.get_randao_reveal(), 0) { - Ok(v) => v, + let randao_reveal = match Signature::ssz_decode(req.get_randao_reveal(), 0) { + Ok((reveal, _index)) => reveal, Err(_) => { // decode error, incorrect signature let log_clone = self.log.clone(); @@ -86,6 +86,8 @@ impl BeaconBlockService for BeaconBlockServiceInstance { req: PublishBeaconBlockRequest, sink: UnarySink, ) { + trace!(&self.log, "Attempting to publish a block"); + let mut resp = PublishBeaconBlockResponse::new(); let ssz_serialized_block = req.get_block().get_ssz(); diff --git a/beacon_node/rpc/src/beacon_chain.rs b/beacon_node/rpc/src/beacon_chain.rs index f21b8df7b4..7de48efa1c 100644 --- a/beacon_node/rpc/src/beacon_chain.rs +++ b/beacon_node/rpc/src/beacon_chain.rs @@ -5,10 +5,10 @@ use beacon_chain::{ parking_lot::RwLockReadGuard, slot_clock::SlotClock, types::{BeaconState, ChainSpec, Signature}, - BlockProductionError, + AttestationValidationError, BlockProductionError, }; pub use beacon_chain::{BeaconChainError, BlockProcessingOutcome}; -use types::BeaconBlock; +use types::{Attestation, AttestationData, BeaconBlock}; /// The RPC's API to the beacon chain. pub trait BeaconChain: Send + Sync { @@ -23,6 +23,13 @@ pub trait BeaconChain: Send + Sync { &self, randao_reveal: Signature, ) -> Result<(BeaconBlock, BeaconState), BlockProductionError>; + + fn produce_attestation_data(&self, shard: u64) -> Result; + + fn process_attestation( + &self, + attestation: Attestation, + ) -> Result<(), AttestationValidationError>; } impl BeaconChain for RawBeaconChain @@ -52,4 +59,15 @@ where ) -> Result<(BeaconBlock, BeaconState), BlockProductionError> { self.produce_block(randao_reveal) } + + fn produce_attestation_data(&self, shard: u64) -> Result { + self.produce_attestation_data(shard) + } + + fn process_attestation( + &self, + attestation: Attestation, + ) -> Result<(), AttestationValidationError> { + self.process_attestation(attestation) + } } diff --git a/beacon_node/rpc/src/lib.rs b/beacon_node/rpc/src/lib.rs index 2d47b4a69a..5aac4ce558 100644 --- a/beacon_node/rpc/src/lib.rs +++ b/beacon_node/rpc/src/lib.rs @@ -1,19 +1,22 @@ +mod attestation; mod beacon_block; pub mod beacon_chain; mod beacon_node; pub mod config; mod validator; +use self::attestation::AttestationServiceInstance; use self::beacon_block::BeaconBlockServiceInstance; 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 futures::Future; +use grpcio::{Environment, ServerBuilder}; use network::NetworkMessage; use protos::services_grpc::{ - create_beacon_block_service, create_beacon_node_service, create_validator_service, + create_attestation_service, create_beacon_block_service, create_beacon_node_service, + create_validator_service, }; use slog::{info, o, warn}; use std::sync::Arc; @@ -56,11 +59,19 @@ pub fn start_server( }; create_validator_service(instance) }; + let attestation_service = { + let instance = AttestationServiceInstance { + chain: beacon_chain.clone(), + log: log.clone(), + }; + create_attestation_service(instance) + }; let mut server = ServerBuilder::new(env) .register_service(beacon_block_service) .register_service(validator_service) .register_service(beacon_node_service) + .register_service(attestation_service) .bind(config.listen_address.to_string(), config.port) .build() .unwrap(); diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 7c231b20b0..8d0aa6af62 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -120,7 +120,7 @@ impl TestingBeaconStateBuilder { }) .collect(); - let genesis_time = 1553918534; // arbitrary + let genesis_time = 1553932445; // arbitrary let mut state = BeaconState::genesis( genesis_time, diff --git a/validator_client/src/attestation_producer/beacon_node_attestation.rs b/validator_client/src/attestation_producer/beacon_node_attestation.rs new file mode 100644 index 0000000000..b5ff777de8 --- /dev/null +++ b/validator_client/src/attestation_producer/beacon_node_attestation.rs @@ -0,0 +1,23 @@ +//TODO: generalise these enums to the crate +use crate::block_producer::{BeaconNodeError, PublishOutcome}; +use types::{Attestation, AttestationData, Slot}; + +/// Defines the methods required to produce and publish attestations on a Beacon Node. Abstracts the +/// actual beacon node. +pub trait BeaconNodeAttestation: Send + Sync { + /// Request that the node produces the required attestation data. + /// + fn produce_attestation_data( + &self, + slot: Slot, + shard: u64, + ) -> Result; + + /// Request that the node publishes a attestation. + /// + /// Returns `true` if the publish was successful. + fn publish_attestation( + &self, + attestation: Attestation, + ) -> Result; +} diff --git a/validator_client/src/attestation_producer/grpc.rs b/validator_client/src/attestation_producer/grpc.rs new file mode 100644 index 0000000000..49c577e242 --- /dev/null +++ b/validator_client/src/attestation_producer/grpc.rs @@ -0,0 +1,59 @@ +use super::beacon_node_attestation::BeaconNodeAttestation; +use crate::block_producer::{BeaconNodeError, PublishOutcome}; +use protos::services_grpc::AttestationServiceClient; +use ssz::{ssz_encode, Decodable}; + +use protos::services::{ + Attestation as GrpcAttestation, ProduceAttestationDataRequest, PublishAttestationRequest, +}; +use types::{Attestation, AttestationData, Slot}; + +impl BeaconNodeAttestation for AttestationServiceClient { + fn produce_attestation_data( + &self, + slot: Slot, + shard: u64, + ) -> Result { + let mut req = ProduceAttestationDataRequest::new(); + req.set_slot(slot.as_u64()); + req.set_shard(shard); + + let reply = self + .produce_attestation_data(&req) + .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + + dbg!("Produced Attestation Data"); + + let (attestation_data, _index) = + AttestationData::ssz_decode(reply.get_attestation_data().get_ssz(), 0) + .map_err(|_| BeaconNodeError::DecodeFailure)?; + Ok(attestation_data) + } + + fn publish_attestation( + &self, + attestation: Attestation, + ) -> Result { + let mut req = PublishAttestationRequest::new(); + + let ssz = ssz_encode(&attestation); + + let mut grpc_attestation = GrpcAttestation::new(); + grpc_attestation.set_ssz(ssz); + + req.set_attestation(grpc_attestation); + + let reply = self + .publish_attestation(&req) + .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; + + if reply.get_success() { + Ok(PublishOutcome::Valid) + } else { + // TODO: distinguish between different errors + Ok(PublishOutcome::InvalidAttestation( + "Publish failed".to_string(), + )) + } + } +} diff --git a/validator_client/src/attestation_producer/mod.rs b/validator_client/src/attestation_producer/mod.rs new file mode 100644 index 0000000000..7b2174b0cd --- /dev/null +++ b/validator_client/src/attestation_producer/mod.rs @@ -0,0 +1,155 @@ +mod beacon_node_attestation; +mod grpc; + +use std::sync::Arc; +use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; +//TODO: Move these higher up in the crate +use super::block_producer::{BeaconNodeError, ValidatorEvent}; +use crate::signer::Signer; +use beacon_node_attestation::BeaconNodeAttestation; +use slog::{error, info, warn}; +use ssz::TreeHash; +use types::{ + AggregateSignature, Attestation, AttestationData, AttestationDataAndCustodyBit, + AttestationDuty, Bitfield, +}; + +//TODO: Group these errors at a crate level +#[derive(Debug, PartialEq)] +pub enum Error { + BeaconNodeError(BeaconNodeError), +} + +impl From for Error { + fn from(e: BeaconNodeError) -> Error { + Error::BeaconNodeError(e) + } +} + +/// This struct contains the logic for requesting and signing beacon attestations for a validator. The +/// validator can abstractly sign via the Signer trait object. +pub struct AttestationProducer<'a, B: BeaconNodeAttestation, S: Signer> { + /// The current fork. + pub fork: Fork, + /// The attestation duty to perform. + pub duty: AttestationDuty, + /// The current epoch. + pub spec: Arc, + /// The beacon node to connect to. + pub beacon_node: Arc, + /// The signer to sign the block. + pub signer: &'a S, +} + +impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { + /// Handle outputs and results from attestation production. + pub fn handle_produce_attestation(&mut self, log: slog::Logger) { + match self.produce_attestation() { + Ok(ValidatorEvent::AttestationProduced(_slot)) => { + info!(log, "Attestation produced"; "Validator" => format!("{}", self.signer)) + } + Err(e) => error!(log, "Attestation production error"; "Error" => format!("{:?}", e)), + Ok(ValidatorEvent::SignerRejection(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Signer could not sign the attestation")) + } + Ok(ValidatorEvent::SlashableAttestationNotProduced(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Rejected the attestation as it could have been slashed")) + } + Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(_slot)) => { + error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to produce an attestation")) + } + Ok(v) => { + warn!(log, "Unknown result for attestation production"; "Error" => format!("{:?}",v)) + } + } + } + + /// Produce an attestation, sign it and send it back + /// + /// Assumes that an attestation is required at this slot (does not check the duties). + /// + /// Ensures the message is not slashable. + /// + /// !!! UNSAFE !!! + /// + /// The slash-protection code is not yet implemented. There is zero protection against + /// slashing. + pub fn produce_attestation(&mut self) -> Result { + let epoch = self.duty.slot.epoch(self.spec.slots_per_epoch); + + let attestation = self + .beacon_node + .produce_attestation_data(self.duty.slot, self.duty.shard)?; + if self.safe_to_produce(&attestation) { + let domain = self.spec.get_domain(epoch, Domain::Attestation, &self.fork); + if let Some(attestation) = self.sign_attestation(attestation, self.duty, domain) { + self.beacon_node.publish_attestation(attestation)?; + Ok(ValidatorEvent::AttestationProduced(self.duty.slot)) + } else { + Ok(ValidatorEvent::SignerRejection(self.duty.slot)) + } + } else { + Ok(ValidatorEvent::SlashableAttestationNotProduced( + self.duty.slot, + )) + } + } + + /// Consumes an attestation, returning the attestation signed by the validators private key. + /// + /// Important: this function will not check to ensure the attestation is not slashable. This must be + /// done upstream. + fn sign_attestation( + &mut self, + mut attestation: AttestationData, + duties: AttestationDuty, + domain: u64, + ) -> Option { + self.store_produce(&attestation); + + // build the aggregate signature + let aggregate_signature = { + let message = AttestationDataAndCustodyBit { + data: attestation.clone(), + custody_bit: false, + } + .hash_tree_root(); + + let sig = self.signer.sign_message(&message, domain)?; + + let mut agg_sig = AggregateSignature::new(); + agg_sig.add(&sig); + agg_sig + }; + + let mut aggregation_bitfield = Bitfield::with_capacity(duties.committee_len); + let custody_bitfield = Bitfield::with_capacity(duties.committee_len); + aggregation_bitfield.set(duties.committee_index, true); + + Some(Attestation { + aggregation_bitfield, + data: attestation, + custody_bitfield, + aggregate_signature, + }) + } + + /// Returns `true` if signing an attestation is safe (non-slashable). + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn safe_to_produce(&self, _attestation: &AttestationData) -> bool { + //TODO: Implement slash protection + true + } + + /// Record that an attestation was produced so that slashable votes may not be made in the future. + /// + /// !!! UNSAFE !!! + /// + /// Important: this function is presently stubbed-out. It provides ZERO SAFETY. + fn store_produce(&mut self, _attestation: &AttestationData) { + // TODO: Implement slash protection + } +} diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index ba0795ec10..91c91e0b10 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -290,6 +290,7 @@ impl Service { // the return value is a future which returns ready. // built to be compatible with the tokio runtime. let _empty = cloned_manager.run_update(current_epoch.clone(), cloned_log.clone()); + dbg!("Duties Thread Ended"); }); } @@ -317,6 +318,7 @@ impl Service { signer, }; block_producer.handle_produce_block(log); + dbg!("Block produce Thread Ended"); }); } if work_type.attestation_duty.is_some() { @@ -338,6 +340,7 @@ impl Service { signer, }; attestation_producer.handle_produce_attestation(log); + dbg!("Attestation Thread Ended"); }); } } From 51ffbc07d25644ef7a8ef0cea64a5d316ec8ec18 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 19:48:45 +1100 Subject: [PATCH 33/57] Correct attestation error handling --- beacon_node/rpc/src/attestation.rs | 5 +++-- .../src/attestation_producer/mod.rs | 21 +++++++++++++++---- validator_client/src/block_producer/mod.rs | 4 ++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index f9423df615..aec948abbd 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -25,7 +25,7 @@ impl AttestationService for AttestationServiceInstance { req: ProduceAttestationDataRequest, sink: UnarySink, ) { - warn!( + trace!( &self.log, "Attempting to produce attestation at slot {}", req.get_slot() @@ -92,7 +92,7 @@ impl AttestationService for AttestationServiceInstance { req: PublishAttestationRequest, sink: UnarySink, ) { - warn!(self.log, "Publishing attestation"); + trace!(self.log, "Publishing attestation"); let mut resp = PublishAttestationResponse::new(); let ssz_serialized_attestation = req.get_attestation().get_ssz(); @@ -128,6 +128,7 @@ impl AttestationService for AttestationServiceInstance { self.log, "PublishAttestation"; "type" => "invalid_attestation", + "error" => format!("{:?}", e), ); resp.set_success(false); resp.set_msg(format!("InvalidAttestation: {:?}", e).as_bytes().to_vec()); diff --git a/validator_client/src/attestation_producer/mod.rs b/validator_client/src/attestation_producer/mod.rs index 7b2174b0cd..db9028c403 100644 --- a/validator_client/src/attestation_producer/mod.rs +++ b/validator_client/src/attestation_producer/mod.rs @@ -4,7 +4,7 @@ mod grpc; use std::sync::Arc; use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; //TODO: Move these higher up in the crate -use super::block_producer::{BeaconNodeError, ValidatorEvent}; +use super::block_producer::{BeaconNodeError, PublishOutcome, ValidatorEvent}; use crate::signer::Signer; use beacon_node_attestation::BeaconNodeAttestation; use slog::{error, info, warn}; @@ -58,6 +58,12 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(_slot)) => { error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to produce an attestation")) } + Ok(ValidatorEvent::PublishAttestationFailed) => { + error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to publish an attestation")) + } + Ok(ValidatorEvent::InvalidAttestation) => { + error!(log, "Attestation production error"; "Error" => format!("The signed attestation was invalid")) + } Ok(v) => { warn!(log, "Unknown result for attestation production"; "Error" => format!("{:?}",v)) } @@ -83,8 +89,15 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { if self.safe_to_produce(&attestation) { let domain = self.spec.get_domain(epoch, Domain::Attestation, &self.fork); if let Some(attestation) = self.sign_attestation(attestation, self.duty, domain) { - self.beacon_node.publish_attestation(attestation)?; - Ok(ValidatorEvent::AttestationProduced(self.duty.slot)) + match self.beacon_node.publish_attestation(attestation) { + Ok(PublishOutcome::InvalidAttestation(_string)) => { + Ok(ValidatorEvent::InvalidAttestation) + } + Ok(PublishOutcome::Valid) => { + Ok(ValidatorEvent::AttestationProduced(self.duty.slot)) + } + Err(_) | Ok(_) => Ok(ValidatorEvent::PublishAttestationFailed), + } } else { Ok(ValidatorEvent::SignerRejection(self.duty.slot)) } @@ -101,7 +114,7 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { /// done upstream. fn sign_attestation( &mut self, - mut attestation: AttestationData, + attestation: AttestationData, duties: AttestationDuty, domain: u64, ) -> Option { diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index 592c0b9192..dc7f6c3eed 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -31,6 +31,10 @@ pub enum ValidatorEvent { BeaconNodeUnableToProduceAttestation(Slot), /// The signer failed to sign the message. SignerRejection(Slot), + /// Publishing an attestation failed. + PublishAttestationFailed, + /// Beacon node rejected the attestation. + InvalidAttestation, } /// This struct contains the logic for requesting and signing beacon blocks for a validator. The From 9a6ecc46657305f4f07e1974e896918244b2a9e7 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sat, 30 Mar 2019 19:58:19 +1100 Subject: [PATCH 34/57] Add clippy suggestions --- .../src/attestation_producer/mod.rs | 13 ++++------ validator_client/src/block_producer/mod.rs | 25 ++++++++----------- validator_client/src/duties/grpc.rs | 5 ++-- validator_client/src/duties/mod.rs | 2 +- validator_client/src/service.rs | 9 +++---- 5 files changed, 22 insertions(+), 32 deletions(-) diff --git a/validator_client/src/attestation_producer/mod.rs b/validator_client/src/attestation_producer/mod.rs index db9028c403..0fbc7bcbaa 100644 --- a/validator_client/src/attestation_producer/mod.rs +++ b/validator_client/src/attestation_producer/mod.rs @@ -2,7 +2,7 @@ mod beacon_node_attestation; mod grpc; use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, Domain, Fork, Slot}; +use types::{ChainSpec, Domain, Fork}; //TODO: Move these higher up in the crate use super::block_producer::{BeaconNodeError, PublishOutcome, ValidatorEvent}; use crate::signer::Signer; @@ -50,19 +50,16 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { } Err(e) => error!(log, "Attestation production error"; "Error" => format!("{:?}", e)), Ok(ValidatorEvent::SignerRejection(_slot)) => { - error!(log, "Attestation production error"; "Error" => format!("Signer could not sign the attestation")) + error!(log, "Attestation production error"; "Error" => "Signer could not sign the attestation".to_string()) } Ok(ValidatorEvent::SlashableAttestationNotProduced(_slot)) => { - error!(log, "Attestation production error"; "Error" => format!("Rejected the attestation as it could have been slashed")) - } - Ok(ValidatorEvent::BeaconNodeUnableToProduceAttestation(_slot)) => { - error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to produce an attestation")) + error!(log, "Attestation production error"; "Error" => "Rejected the attestation as it could have been slashed".to_string()) } Ok(ValidatorEvent::PublishAttestationFailed) => { - error!(log, "Attestation production error"; "Error" => format!("Beacon node was unable to publish an attestation")) + error!(log, "Attestation production error"; "Error" => "Beacon node was unable to publish an attestation".to_string()) } Ok(ValidatorEvent::InvalidAttestation) => { - error!(log, "Attestation production error"; "Error" => format!("The signed attestation was invalid")) + error!(log, "Attestation production error"; "Error" => "The signed attestation was invalid".to_string()) } Ok(v) => { warn!(log, "Unknown result for attestation production"; "Error" => format!("{:?}",v)) diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index dc7f6c3eed..8b4f5abda0 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -27,8 +27,6 @@ pub enum ValidatorEvent { SlashableAttestationNotProduced(Slot), /// The Beacon Node was unable to produce a block at that slot. BeaconNodeUnableToProduceBlock(Slot), - /// The Beacon Node was unable to produce an attestation at that slot. - BeaconNodeUnableToProduceAttestation(Slot), /// The signer failed to sign the message. SignerRejection(Slot), /// Publishing an attestation failed. @@ -61,13 +59,13 @@ impl<'a, B: BeaconNodeBlock, S: Signer> BlockProducer<'a, B, S> { } Err(e) => error!(log, "Block production error"; "Error" => format!("{:?}", e)), Ok(ValidatorEvent::SignerRejection(_slot)) => { - error!(log, "Block production error"; "Error" => format!("Signer Could not sign the block")) + error!(log, "Block production error"; "Error" => "Signer Could not sign the block".to_string()) } Ok(ValidatorEvent::SlashableBlockNotProduced(_slot)) => { - error!(log, "Block production error"; "Error" => format!("Rejected the block as it could have been slashed")) + error!(log, "Block production error"; "Error" => "Rejected the block as it could have been slashed".to_string()) } Ok(ValidatorEvent::BeaconNodeUnableToProduceBlock(_slot)) => { - error!(log, "Block production error"; "Error" => format!("Beacon node was unable to produce a block")) + error!(log, "Block production error"; "Error" => "Beacon node was unable to produce a block".to_string()) } Ok(v) => { warn!(log, "Unknown result for block production"; "Error" => format!("{:?}",v)) @@ -88,16 +86,13 @@ impl<'a, B: BeaconNodeBlock, S: Signer> BlockProducer<'a, B, S> { pub fn produce_block(&mut self) -> Result { let epoch = self.slot.epoch(self.spec.slots_per_epoch); - let randao_reveal = { - let message = epoch.hash_tree_root(); - let randao_reveal = match self.signer.sign_message( - &message, - self.spec.get_domain(epoch, Domain::Randao, &self.fork), - ) { - None => return Ok(ValidatorEvent::SignerRejection(self.slot)), - Some(signature) => signature, - }; - randao_reveal + let message = epoch.hash_tree_root(); + let randao_reveal = match self.signer.sign_message( + &message, + self.spec.get_domain(epoch, Domain::Randao, &self.fork), + ) { + None => return Ok(ValidatorEvent::SignerRejection(self.slot)), + Some(signature) => signature, }; if let Some(block) = self diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index ab87b602e0..954ee194e7 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -1,11 +1,12 @@ use super::beacon_node_duties::{BeaconNodeDuties, BeaconNodeDutiesError}; use super::epoch_duties::{EpochDuties, EpochDuty}; -use grpcio::CallOption; +// to use if we manually specify a timeout +//use grpcio::CallOption; use protos::services::{GetDutiesRequest, Validators}; use protos::services_grpc::ValidatorServiceClient; use ssz::ssz_encode; use std::collections::HashMap; -use std::time::Duration; +// use std::time::Duration; use types::{AttestationDuty, Epoch, PublicKey, Slot}; impl BeaconNodeDuties for ValidatorServiceClient { diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index d52cc32544..7db4672e30 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -69,7 +69,7 @@ impl DutiesManager { // duties have changed //TODO: Duties could be large here. Remove from display and avoid the clone. self.duties_map.write()?.insert(epoch, duties.clone()); - return Ok(UpdateOutcome::DutiesChanged(epoch, duties)); + Ok(UpdateOutcome::DutiesChanged(epoch, duties)) } /// A future wrapping around `update()`. This will perform logic based upon the update diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 91c91e0b10..ce9e352665 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -157,7 +157,7 @@ impl Service { let current_slot = slot_clock .present_slot() - .map_err(|e| ErrorKind::SlotClockError(e))? + .map_err(ErrorKind::SlotClockError)? .expect("Genesis must be in the future"); /* Generate the duties manager */ @@ -289,8 +289,7 @@ impl Service { std::thread::spawn(move || { // the return value is a future which returns ready. // built to be compatible with the tokio runtime. - let _empty = cloned_manager.run_update(current_epoch.clone(), cloned_log.clone()); - dbg!("Duties Thread Ended"); + let _empty = cloned_manager.run_update(current_epoch, cloned_log.clone()); }); } @@ -303,7 +302,7 @@ impl Service { // spawns a thread to produce a beacon block let signers = self.duties_manager.signers.clone(); // this is an arc let fork = self.fork.clone(); - let slot = self.current_slot.clone(); + let slot = self.current_slot; let spec = self.spec.clone(); let beacon_node = self.beacon_block_client.clone(); let log = self.log.clone(); @@ -318,7 +317,6 @@ impl Service { signer, }; block_producer.handle_produce_block(log); - dbg!("Block produce Thread Ended"); }); } if work_type.attestation_duty.is_some() { @@ -340,7 +338,6 @@ impl Service { signer, }; attestation_producer.handle_produce_attestation(log); - dbg!("Attestation Thread Ended"); }); } } From 4fb95d06d1062e1779f24d4cda23a89c4f9a6f9f Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 31 Mar 2019 00:08:55 +1100 Subject: [PATCH 35/57] Correct cache race condition --- beacon_node/rpc/src/beacon_chain.rs | 8 +++++++- beacon_node/rpc/src/validator.rs | 18 ++++++++++++++---- .../test_utils/testing_beacon_state_builder.rs | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/beacon_node/rpc/src/beacon_chain.rs b/beacon_node/rpc/src/beacon_chain.rs index 7de48efa1c..ddc91b73cc 100644 --- a/beacon_node/rpc/src/beacon_chain.rs +++ b/beacon_node/rpc/src/beacon_chain.rs @@ -2,7 +2,7 @@ use beacon_chain::BeaconChain as RawBeaconChain; use beacon_chain::{ db::ClientDB, fork_choice::ForkChoice, - parking_lot::RwLockReadGuard, + parking_lot::{RwLockReadGuard, RwLockWriteGuard}, slot_clock::SlotClock, types::{BeaconState, ChainSpec, Signature}, AttestationValidationError, BlockProductionError, @@ -16,6 +16,8 @@ pub trait BeaconChain: Send + Sync { fn get_state(&self) -> RwLockReadGuard; + fn get_mut_state(&self) -> RwLockWriteGuard; + fn process_block(&self, block: BeaconBlock) -> Result; @@ -46,6 +48,10 @@ where self.state.read() } + fn get_mut_state(&self) -> RwLockWriteGuard { + self.state.write() + } + fn process_block( &self, block: BeaconBlock, diff --git a/beacon_node/rpc/src/validator.rs b/beacon_node/rpc/src/validator.rs index 936c95f52f..a60cb43942 100644 --- a/beacon_node/rpc/src/validator.rs +++ b/beacon_node/rpc/src/validator.rs @@ -4,7 +4,7 @@ use futures::Future; use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink}; use protos::services::{ActiveValidator, GetDutiesRequest, GetDutiesResponse, ValidatorDuty}; use protos::services_grpc::ValidatorService; -use slog::{debug, info, warn, Logger}; +use slog::{trace, warn}; use ssz::Decodable; use std::sync::Arc; use types::{Epoch, RelativeEpoch}; @@ -12,7 +12,7 @@ use types::{Epoch, RelativeEpoch}; #[derive(Clone)] pub struct ValidatorServiceInstance { pub chain: Arc, - pub log: Logger, + pub log: slog::Logger, } //TODO: Refactor Errors @@ -27,13 +27,23 @@ impl ValidatorService for ValidatorServiceInstance { sink: UnarySink, ) { let validators = req.get_validators(); - debug!(self.log, "RPC request"; "endpoint" => "GetValidatorDuties", "epoch" => req.get_epoch()); + trace!(self.log, "RPC request"; "endpoint" => "GetValidatorDuties", "epoch" => req.get_epoch()); + + let spec = self.chain.get_spec(); + // update the caches if necessary + { + let mut mut_state = self.chain.get_mut_state(); + + let _ = mut_state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec); + + let _ = mut_state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec); + let _ = mut_state.update_pubkey_cache(); + } let epoch = Epoch::from(req.get_epoch()); let mut resp = GetDutiesResponse::new(); let resp_validators = resp.mut_active_validators(); - let spec = self.chain.get_spec(); let state = self.chain.get_state(); let relative_epoch = diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 8d0aa6af62..c041889208 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -120,7 +120,7 @@ impl TestingBeaconStateBuilder { }) .collect(); - let genesis_time = 1553932445; // arbitrary + let genesis_time = 1553950542; // arbitrary let mut state = BeaconState::genesis( genesis_time, From ee693fb3e3942ad175cc5f441903795afdedcf16 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 31 Mar 2019 00:34:35 +1100 Subject: [PATCH 36/57] Add committe_len to gRPC parameters --- beacon_node/rpc/src/attestation.rs | 2 +- beacon_node/rpc/src/validator.rs | 1 + protos/src/services.proto | 1 + validator_client/src/duties/grpc.rs | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index aec948abbd..c6e5c68eeb 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -6,7 +6,7 @@ use protos::services::{ ProduceAttestationDataResponse, PublishAttestationRequest, PublishAttestationResponse, }; use protos::services_grpc::AttestationService; -use slog::{error, info, trace, warn, Logger}; +use slog::{error, info, trace, warn}; use ssz::{ssz_encode, Decodable}; use std::sync::Arc; use types::Attestation; diff --git a/beacon_node/rpc/src/validator.rs b/beacon_node/rpc/src/validator.rs index a60cb43942..3dbbbdd177 100644 --- a/beacon_node/rpc/src/validator.rs +++ b/beacon_node/rpc/src/validator.rs @@ -167,6 +167,7 @@ impl ValidatorService for ValidatorServiceInstance { duty.set_committee_index(attestation_duties.committee_index as u64); duty.set_attestation_slot(attestation_duties.slot.as_u64()); duty.set_attestation_shard(attestation_duties.shard); + duty.set_committee_len(attestation_duties.committee_len as u64); active_validator.set_duty(duty); resp_validators.push(active_validator); diff --git a/protos/src/services.proto b/protos/src/services.proto index c5bd8a91d3..ecc75ee264 100644 --- a/protos/src/services.proto +++ b/protos/src/services.proto @@ -125,6 +125,7 @@ message ValidatorDuty { uint64 attestation_slot = 3; uint64 attestation_shard = 4; uint64 committee_index = 5; + uint64 committee_len = 6; } /* diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index 954ee194e7..58fb5c992d 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -53,7 +53,7 @@ impl BeaconNodeDuties for ValidatorServiceClient { slot: Slot::from(active_duty.get_attestation_slot()), shard: active_duty.get_attestation_shard(), committee_index: active_duty.get_committee_index() as usize, - committee_len: 10, + committee_len: active_duty.get_committee_len() as usize, }; let epoch_duty = EpochDuty { From d2b5cf5a32eeee3ff69dbcf436f2dfba23600891 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 09:44:58 +1100 Subject: [PATCH 37/57] Improve queueing in SimpleSync --- beacon_node/eth2-libp2p/src/rpc/methods.rs | 13 ++++ beacon_node/network/src/sync/import_queue.rs | 54 ++++++++++--- beacon_node/network/src/sync/simple_sync.rs | 78 +++++++++++++------ .../testing_beacon_state_builder.rs | 2 +- 4 files changed, 115 insertions(+), 32 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index ad3233be71..f9adb93c17 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -179,6 +179,19 @@ pub struct BeaconBlockRootsResponse { pub roots: Vec, } +impl BeaconBlockRootsResponse { + /// Returns `true` if each `self.roots.slot[i]` is higher than the preceeding `i`. + pub fn slots_are_ascending(&self) -> bool { + for i in 1..self.roots.len() { + if self.roots[i - 1].slot >= self.roots[i].slot { + return false; + } + } + + true + } +} + /// Contains a block root and associated slot. #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub struct BlockRootSlot { diff --git a/beacon_node/network/src/sync/import_queue.rs b/beacon_node/network/src/sync/import_queue.rs index 17cbd2f12e..8680993aa4 100644 --- a/beacon_node/network/src/sync/import_queue.rs +++ b/beacon_node/network/src/sync/import_queue.rs @@ -5,7 +5,7 @@ use slog::{debug, error}; use ssz::TreeHash; use std::sync::Arc; use std::time::{Duration, Instant}; -use types::{BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Hash256}; +use types::{BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Hash256, Slot}; /// Provides a queue for fully and partially built `BeaconBlock`s. /// @@ -120,6 +120,38 @@ impl ImportQueue { .position(|brs| self.is_new_block(&brs.block_root)) } + /// Adds the `block_roots` to the partials queue. + /// + /// If a `block_root` is not in the queue and has not been processed by the chain it is added + /// to the queue and it's block root is included in the output. + pub fn enqueue_block_roots( + &mut self, + block_roots: &[BlockRootSlot], + sender: PeerId, + ) -> Vec { + let new_roots: Vec = block_roots + .iter() + // Ignore any roots already processed by the chain. + .filter(|brs| self.is_new_block(&brs.block_root)) + // Ignore any roots already stored in the queue. + .filter(|brs| !self.partials.iter().any(|p| p.block_root == brs.block_root)) + .cloned() + .collect(); + + new_roots.iter().for_each(|brs| { + self.partials.push(PartialBeaconBlock { + slot: brs.slot, + block_root: brs.block_root, + sender: sender.clone(), + header: None, + body: None, + inserted: Instant::now(), + }) + }); + + new_roots + } + /// Adds the `headers` to the `partials` queue. Returns a list of `Hash256` block roots for /// which we should use to request `BeaconBlockBodies`. /// @@ -174,8 +206,9 @@ impl ImportQueue { self.partials[i].inserted = Instant::now(); } else { self.partials.push(PartialBeaconBlock { + slot: header.slot, block_root, - header, + header: Some(header), body: None, inserted: Instant::now(), sender, @@ -192,12 +225,14 @@ impl ImportQueue { let body_root = Hash256::from_slice(&body.hash_tree_root()[..]); self.partials.iter_mut().for_each(|mut p| { - if body_root == p.header.block_body_root { - p.inserted = Instant::now(); + if let Some(header) = &mut p.header { + if body_root == header.block_body_root { + p.inserted = Instant::now(); - if p.body.is_none() { - p.body = Some(body.clone()); - p.sender = sender.clone(); + if p.body.is_none() { + p.body = Some(body.clone()); + p.sender = sender.clone(); + } } } }); @@ -208,9 +243,10 @@ impl ImportQueue { /// `BeaconBlock`. #[derive(Clone, Debug)] pub struct PartialBeaconBlock { + pub slot: Slot, /// `BeaconBlock` root. pub block_root: Hash256, - pub header: BeaconBlockHeader, + pub header: Option, pub body: Option, /// The instant at which this record was created or last meaningfully modified. Used to /// determine if an entry is stale and should be removed. @@ -225,7 +261,7 @@ impl PartialBeaconBlock { pub fn complete(self) -> Option<(Hash256, BeaconBlock, PeerId)> { Some(( self.block_root, - self.header.into_block(self.body?), + self.header?.into_block(self.body?), self.sender, )) } diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 1afba018d9..21b2612689 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -358,31 +358,48 @@ impl SimpleSync { if res.roots.is_empty() { warn!( self.log, - "Peer returned empty block roots response. PeerId: {:?}", peer_id + "Peer returned empty block roots response"; + "peer_id" => format!("{:?}", peer_id) ); return; } - let new_root_index = self.import_queue.first_new_root(&res.roots); - - // If a new block root is found, request it and all the headers following it. - // - // We make an assumption here that if we don't know a block then we don't know of all - // it's parents. This might not be the case if syncing becomes more sophisticated. - if let Some(i) = new_root_index { - let new = &res.roots[i]; - - self.request_block_headers( - peer_id, - BeaconBlockHeadersRequest { - start_root: new.block_root, - start_slot: new.slot, - max_headers: (res.roots.len() - i) as u64, - skip_slots: 0, - }, - network, - ) + // The wire protocol specifies that slots must be in ascending order. + if !res.slots_are_ascending() { + warn!( + self.log, + "Peer returned block roots response with bad slot ordering"; + "peer_id" => format!("{:?}", peer_id) + ); + return; } + + let new_roots = self.import_queue.enqueue_block_roots(&res.roots, peer_id.clone()); + + // No new roots means nothing to do. + // + // This check protects against future panics. + if new_roots.is_empty() { + return; + } + + // Determine the first (earliest) and last (latest) `BlockRootSlot` items. + // + // This logic relies upon slots to be in ascending order, which is enforced earlier. + let first = new_roots.first().expect("Non-empty list must have first"); + let last = new_roots.last().expect("Non-empty list must have last"); + + // Request all headers between the earliest and latest new `BlockRootSlot` items. + self.request_block_headers( + peer_id, + BeaconBlockHeadersRequest { + start_root: first.block_root, + start_slot: first.slot, + max_headers: (last.slot - first.slot + 1).as_u64(), + skip_slots: 0, + }, + network, + ) } /// Handle a `BeaconBlockHeaders` request from the peer. @@ -528,8 +545,17 @@ impl SimpleSync { "NewGossipBlock"; "peer" => format!("{:?}", peer_id), ); - // TODO: filter out messages that a prior to the finalized slot. - // + + // Ignore any block from a finalized slot. + if self.slot_is_finalized(msg.slot) { + warn!( + self.log, "NewGossipBlock"; + "msg" => "new block slot is finalized.", + "slot" => msg.slot, + ); + return; + } + // TODO: if the block is a few more slots ahead, try to get all block roots from then until // now. // @@ -675,6 +701,14 @@ impl SimpleSync { network.send_rpc_request(peer_id.clone(), RPCRequest::BeaconBlockBodies(req)); } + fn slot_is_finalized(&self, slot: Slot) -> bool { + slot <= self + .chain + .hello_message() + .latest_finalized_epoch + .start_slot(self.chain.get_spec().slots_per_epoch) + } + /// Generates our current state in the form of a HELLO RPC message. pub fn generate_hello(&self) -> HelloMessage { self.chain.hello_message() diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index b38e8b5273..f437240dc6 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -120,7 +120,7 @@ impl TestingBeaconStateBuilder { }) .collect(); - let genesis_time = 1553753928; // arbitrary + let genesis_time = 1553977336; // arbitrary let mut state = BeaconState::genesis( genesis_time, From 5cc2fdd3d4bb932196da410f852082442c5e43d0 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 10:15:01 +1100 Subject: [PATCH 38/57] Change `beacon_node` and `network` slog features Allows printing debug messages in --release --- beacon_node/Cargo.toml | 2 +- beacon_node/network/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index a090c1cc54..37d96a4974 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -9,7 +9,7 @@ types = { path = "../eth2/types" } client = { path = "client" } version = { path = "version" } clap = "2.32.0" -slog = "^2.2.3" +slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } slog-term = "^2.4.0" slog-async = "^2.3.0" ctrlc = { version = "3.1.1", features = ["termination"] } diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index c6411a0205..cd2c2269a6 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -13,7 +13,7 @@ beacon_chain = { path = "../beacon_chain" } eth2-libp2p = { path = "../eth2-libp2p" } version = { path = "../version" } types = { path = "../../eth2/types" } -slog = "2.4.1" +slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] } ssz = { path = "../../eth2/utils/ssz" } futures = "0.1.25" error-chain = "0.12.0" From c99a742aae515564a575fc65279dca18cb92d5fe Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 10:15:42 +1100 Subject: [PATCH 39/57] Fix bug in SimpleSync queue. It was not completing partials with bodies. --- beacon_node/network/src/sync/import_queue.rs | 16 +++++++++------- beacon_node/network/src/sync/simple_sync.rs | 4 +++- beacon_node/network/tests/tests.rs | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/beacon_node/network/src/sync/import_queue.rs b/beacon_node/network/src/sync/import_queue.rs index 8680993aa4..b9280440bf 100644 --- a/beacon_node/network/src/sync/import_queue.rs +++ b/beacon_node/network/src/sync/import_queue.rs @@ -113,13 +113,6 @@ impl ImportQueue { }) } - /// Returns the index of the first new root in the list of block roots. - pub fn first_new_root(&mut self, roots: &[BlockRootSlot]) -> Option { - roots - .iter() - .position(|brs| self.is_new_block(&brs.block_root)) - } - /// Adds the `block_roots` to the partials queue. /// /// If a `block_root` is not in the queue and has not been processed by the chain it is added @@ -203,8 +196,17 @@ impl ImportQueue { .iter() .position(|p| p.block_root == block_root) { + // Case 1: there already exists a partial with a matching block root. + // + // The `inserted` time is set to now and the header is replaced, regardless of whether + // it existed or not. + self.partials[i].header = Some(header); self.partials[i].inserted = Instant::now(); } else { + // Case 2: there was no partial with a matching block root. + // + // A new partial is added. This case permits adding a header without already known the + // root -- this is not possible in the wire protocol however we support it anyway. self.partials.push(PartialBeaconBlock { slot: header.slot, block_root, diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 21b2612689..39fe772b45 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -374,7 +374,9 @@ impl SimpleSync { return; } - let new_roots = self.import_queue.enqueue_block_roots(&res.roots, peer_id.clone()); + let new_roots = self + .import_queue + .enqueue_block_roots(&res.roots, peer_id.clone()); // No new roots means nothing to do. // diff --git a/beacon_node/network/tests/tests.rs b/beacon_node/network/tests/tests.rs index 9cead1b557..47d5482d3e 100644 --- a/beacon_node/network/tests/tests.rs +++ b/beacon_node/network/tests/tests.rs @@ -543,7 +543,7 @@ fn sync_two_nodes() { // A provides block bodies to B. node_a.tee_block_body_response(&node_b); - std::thread::sleep(Duration::from_secs(10)); + std::thread::sleep(Duration::from_secs(20)); node_b.harness.run_fork_choice(); From e0680f97711b64b97e477e0d948ca968d59d6ab7 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 31 Mar 2019 11:04:50 +1100 Subject: [PATCH 40/57] Correct compile error --- beacon_node/beacon_chain/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 7605ced156..d8d85a8a6c 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -4,9 +4,7 @@ mod errors; pub mod initialise; pub mod test_utils; -pub use self::beacon_chain::{ - AttestationValidationError, BeaconChain, BlockProcessingOutcome, InvalidBlock, ValidBlock, -}; +pub use self::beacon_chain::{BeaconChain, BlockProcessingOutcome, InvalidBlock, ValidBlock}; pub use self::checkpoint::CheckPoint; pub use self::errors::{BeaconChainError, BlockProductionError}; pub use db; From 2c1fa86cd317b970aed03fbebe23329051f54062 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 12:28:35 +1100 Subject: [PATCH 41/57] Swap to gossiping whole block. Processing for gossiped blocks is broken in `SimpleSync`, will be fixed next. --- beacon_node/eth2-libp2p/src/behaviour.rs | 13 +++----- beacon_node/network/src/sync/simple_sync.rs | 35 ++++++++++++++++----- beacon_node/rpc/src/beacon_block.rs | 33 ++++++++++--------- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index 2865971838..fdf12f0bff 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -1,4 +1,3 @@ -use crate::rpc::methods::BlockRootSlot; use crate::rpc::{RPCEvent, RPCMessage, Rpc}; use crate::NetworkConfig; use futures::prelude::*; @@ -15,8 +14,7 @@ use libp2p::{ }; use slog::{debug, o, warn}; use ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream}; -use ssz_derive::{Decode, Encode}; -use types::Attestation; +use types::{Attestation, BeaconBlock}; use types::{Topic, TopicHash}; /// Builds the network behaviour for the libp2p Swarm. @@ -198,7 +196,7 @@ pub enum BehaviourEvent { #[derive(Debug, Clone, PartialEq)] pub enum PubsubMessage { /// Gossipsub message providing notification of a new block. - Block(BlockRootSlot), + Block(BeaconBlock), /// Gossipsub message providing notification of a new attestation. Attestation(Attestation), } @@ -224,7 +222,7 @@ impl Decodable for PubsubMessage { let (id, index) = u32::ssz_decode(bytes, index)?; match id { 0 => { - let (block, index) = BlockRootSlot::ssz_decode(bytes, index)?; + let (block, index) = BeaconBlock::ssz_decode(bytes, index)?; Ok((PubsubMessage::Block(block), index)) } 1 => { @@ -243,10 +241,7 @@ mod test { #[test] fn ssz_encoding() { - let original = PubsubMessage::Block(BlockRootSlot { - block_root: Hash256::from_slice(&[42; 32]), - slot: Slot::new(4), - }); + let original = PubsubMessage::Block(BeaconBlock::empty(&ChainSpec::foundation())); let encoded = ssz_encode(&original); diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 39fe772b45..e8a3da656d 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -8,7 +8,7 @@ use slog::{debug, error, info, o, warn}; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; -use types::{Attestation, Epoch, Hash256, Slot}; +use types::{Attestation, BeaconBlock, 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; @@ -539,7 +539,7 @@ impl SimpleSync { pub fn on_block_gossip( &mut self, peer_id: PeerId, - msg: BlockRootSlot, + block: BeaconBlock, network: &mut NetworkContext, ) { info!( @@ -548,6 +548,7 @@ impl SimpleSync { "peer" => format!("{:?}", peer_id), ); + /* // Ignore any block from a finalized slot. if self.slot_is_finalized(msg.slot) { warn!( @@ -558,11 +559,13 @@ impl SimpleSync { return; } - // TODO: if the block is a few more slots ahead, try to get all block roots from then until - // now. - // - // Note: only requests the new block -- will fail if we don't have its parents. - if self.import_queue.is_new_block(&msg.block_root) { + // Ignore any block that the chain already knows about. + if self.chain_has_seen_block(&msg.block_root) { + return; + } + + // k + if msg.slot == self.chain.hello_message().best_slot + 1 { self.request_block_headers( peer_id, BeaconBlockHeadersRequest { @@ -574,6 +577,24 @@ impl SimpleSync { network, ) } + + // TODO: if the block is a few more slots ahead, try to get all block roots from then until + // now. + // + // Note: only requests the new block -- will fail if we don't have its parents. + if !self.chain_has_seen_block(&msg.block_root) { + self.request_block_headers( + peer_id, + BeaconBlockHeadersRequest { + start_root: msg.block_root, + start_slot: msg.slot, + max_headers: 1, + skip_slots: 0, + }, + network, + ) + } + */ } /// Process a gossip message declaring a new attestation. diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index f6b426c18f..6978e0f0e8 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -1,6 +1,5 @@ use crate::beacon_chain::BeaconChain; use crossbeam_channel; -use eth2_libp2p::rpc::methods::BlockRootSlot; use eth2_libp2p::PubsubMessage; use futures::Future; use grpcio::{RpcContext, UnarySink}; @@ -11,10 +10,10 @@ use protos::services::{ }; use protos::services_grpc::BeaconBlockService; use slog::Logger; -use slog::{debug, error, info, warn}; -use ssz::{Decodable, TreeHash}; +use slog::{error, info, warn}; +use ssz::Decodable; use std::sync::Arc; -use types::{BeaconBlock, Hash256, Slot}; +use types::BeaconBlock; #[derive(Clone)] pub struct BeaconBlockServiceInstance { @@ -59,8 +58,6 @@ impl BeaconBlockService for BeaconBlockServiceInstance { match BeaconBlock::ssz_decode(ssz_serialized_block, 0) { Ok((block, _i)) => { - let block_root = Hash256::from_slice(&block.hash_tree_root()[..]); - match self.chain.process_block(block.clone()) { Ok(outcome) => { if outcome.sucessfully_processed() { @@ -76,16 +73,22 @@ impl BeaconBlockService for BeaconBlockServiceInstance { // TODO: Obtain topics from the network service properly. let topic = types::TopicBuilder::new("beacon_chain".to_string()).build(); - let message = PubsubMessage::Block(BlockRootSlot { - block_root, - slot: block.slot, - }); + let message = PubsubMessage::Block(block); - println!("Sending beacon block to gossipsub"); - self.network_chan.send(NetworkMessage::Publish { - topics: vec![topic], - message, - }); + // Publish the block to the p2p network via gossipsub. + self.network_chan + .send(NetworkMessage::Publish { + topics: vec![topic], + message, + }) + .unwrap_or_else(|e| { + error!( + self.log, + "PublishBeaconBlock"; + "type" => "failed to publish to gossipsub", + "error" => format!("{:?}", e) + ); + }); resp.set_success(true); } else if outcome.is_invalid() { From 4e71ed69721dd7d665169a043389e7c3062679b9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 12:54:42 +1100 Subject: [PATCH 42/57] Fix `produce_attestation` bug. It was referencing the wrong crosslink. --- beacon_node/beacon_chain/src/beacon_chain.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 6ca0bff733..efa83bdd39 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -492,10 +492,7 @@ where beacon_block_root: self.head().beacon_block_root, target_root, crosslink_data_root: Hash256::zero(), - previous_crosslink: Crosslink { - epoch: self.state.read().slot.epoch(self.spec.slots_per_epoch), - crosslink_data_root: Hash256::zero(), - }, + previous_crosslink: state.latest_crosslinks[shard as usize].clone(), source_epoch: state.current_justified_epoch, source_root: state.current_justified_root, }) From b26f1f8e1cfd357f2991d1e8230a52305f491a44 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 13:40:12 +1100 Subject: [PATCH 43/57] Add `build_all_caches` method to `BeaconState` Also adds a few more cache builds in BeaconChain. --- beacon_node/beacon_chain/src/beacon_chain.rs | 23 ++++++++++++-------- eth2/types/src/beacon_state.rs | 11 ++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index efa83bdd39..046f37a81a 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -130,10 +130,7 @@ where state_root, )); - genesis_state.build_epoch_cache(RelativeEpoch::Previous, &spec)?; - genesis_state.build_epoch_cache(RelativeEpoch::Current, &spec)?; - genesis_state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)?; - genesis_state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)?; + genesis_state.build_all_caches(&spec)?; Ok(Self { block_store, @@ -318,6 +315,8 @@ where per_slot_processing(&mut state, &latest_block_header, &self.spec)?; } + state.build_all_caches(&self.spec)?; + *self.state.write() = state; Ok(()) @@ -342,11 +341,17 @@ where per_slot_processing(&mut *state, &latest_block_header, &self.spec)?; } - state.build_epoch_cache(RelativeEpoch::Previous, &self.spec)?; - state.build_epoch_cache(RelativeEpoch::Current, &self.spec)?; - state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &self.spec)?; - state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &self.spec)?; - state.update_pubkey_cache()?; + + state.build_all_caches(&self.spec)?; + + Ok(()) + } + + /// Build all of the caches on the current state. + /// + /// Ideally this shouldn't be required, however we leave it here for testing. + pub fn ensure_state_caches_are_built(&self) -> Result<(), Error> { + self.state.write().build_all_caches(&self.spec)?; Ok(()) } diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 1e52781243..774e8eb761 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -661,6 +661,17 @@ impl BeaconState { }) } + /// Build all the caches, if they need to be built. + pub fn build_all_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> { + self.build_epoch_cache(RelativeEpoch::Previous, spec)?; + self.build_epoch_cache(RelativeEpoch::Current, spec)?; + self.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec)?; + self.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?; + self.update_pubkey_cache()?; + + Ok(()) + } + /// Build an epoch cache, unless it is has already been built. pub fn build_epoch_cache( &mut self, From 33473892f27df37fdec79fb4a7d76c8fe241083c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 31 Mar 2019 14:26:58 +1100 Subject: [PATCH 44/57] Validator client fixes. Hack fix for genesis start time --- beacon_node/rpc/src/attestation.rs | 24 ++++++++++++++++--- beacon_node/rpc/src/validator.rs | 13 +--------- .../validate_attestation.rs | 3 +++ .../testing_beacon_state_builder.rs | 10 +++++++- validator_client/src/service.rs | 8 ++++++- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index c6e5c68eeb..494b240678 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -37,14 +37,29 @@ impl AttestationService for AttestationServiceInstance { let state = self.chain.get_state(); // Start by performing some checks - // Check that the the AttestionData is for the current slot (otherwise it will not be valid) - if slot_requested != state.slot.as_u64() { + // Check that the AttestionData is for the current slot (otherwise it will not be valid) + if slot_requested > state.slot.as_u64() { let log_clone = self.log.clone(); let f = sink .fail(RpcStatus::new( RpcStatusCode::OutOfRange, Some(format!( - "AttestationData request for a slot that is not the current slot." + "AttestationData request for a slot that is in the future." + )), + )) + .map_err(move |e| { + error!(log_clone, "Failed to reply with failure {:?}: {:?}", req, e) + }); + return ctx.spawn(f); + } + // currently cannot handle past slots. TODO: Handle this case + else if slot_requested < state.slot.as_u64() { + let log_clone = self.log.clone(); + let f = sink + .fail(RpcStatus::new( + RpcStatusCode::InvalidArgument, + Some(format!( + "AttestationData request for a slot that is in the past." )), )) .map_err(move |e| { @@ -71,6 +86,9 @@ impl AttestationService for AttestationServiceInstance { } }; + dbg!("Produced attestation"); + dbg!(attestation_data.clone()); + let mut attestation_data_proto = AttestationDataProto::new(); attestation_data_proto.set_ssz(ssz_encode(&attestation_data)); diff --git a/beacon_node/rpc/src/validator.rs b/beacon_node/rpc/src/validator.rs index 3dbbbdd177..0a9d7015c9 100644 --- a/beacon_node/rpc/src/validator.rs +++ b/beacon_node/rpc/src/validator.rs @@ -30,22 +30,11 @@ impl ValidatorService for ValidatorServiceInstance { trace!(self.log, "RPC request"; "endpoint" => "GetValidatorDuties", "epoch" => req.get_epoch()); let spec = self.chain.get_spec(); - // update the caches if necessary - { - let mut mut_state = self.chain.get_mut_state(); - - let _ = mut_state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec); - - let _ = mut_state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec); - let _ = mut_state.update_pubkey_cache(); - } - + let state = self.chain.get_state(); let epoch = Epoch::from(req.get_epoch()); let mut resp = GetDutiesResponse::new(); let resp_validators = resp.mut_active_validators(); - let state = self.chain.get_state(); - let relative_epoch = match RelativeEpoch::from_epoch(state.slot.epoch(spec.slots_per_epoch), epoch) { Ok(v) => v, diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 3b89bec99c..76b4157968 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -99,6 +99,9 @@ fn validate_attestation_parametric( crosslink_data_root: attestation.data.crosslink_data_root, epoch: attestation.data.slot.epoch(spec.slots_per_epoch), }; + dbg!(attestation.clone()); + dbg!(state.latest_crosslinks[attestation.data.shard as usize].clone()); + dbg!(potential_crosslink.clone()); verify!( (attestation.data.previous_crosslink == state.latest_crosslinks[attestation.data.shard as usize]) diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index f437240dc6..518f55e3c1 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -6,6 +6,8 @@ use dirs; use log::debug; use rayon::prelude::*; use std::path::{Path, PathBuf}; +//TODO: testing only +use std::time::{Duration, SystemTime}; pub const KEYPAIRS_FILE: &str = "keypairs.raw_keypairs"; @@ -120,7 +122,13 @@ impl TestingBeaconStateBuilder { }) .collect(); - let genesis_time = 1553977336; // arbitrary + //TODO: Testing only + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + - 30; + let genesis_time = now; // arbitrary let mut state = BeaconState::genesis( genesis_time, diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index ce9e352665..38883e68fa 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -29,11 +29,15 @@ use std::sync::RwLock; use std::time::{Duration, Instant, SystemTime}; use tokio::prelude::*; use tokio::runtime::Builder; -use tokio::timer::Interval; +use tokio::timer::{Delay, Interval}; use tokio_timer::clock::Clock; use types::test_utils::generate_deterministic_keypairs; use types::{ChainSpec, Epoch, Fork, Slot}; +/// A fixed amount of time after a slot to perform operations. This gives the node time to complete +/// per-slot processes. +const TIME_DELAY_FROM_SLOT: Duration = Duration::from_millis(200); + /// The validator service. This is the main thread that executes and maintains validator /// duties. //TODO: Generalize the BeaconNode types to use testing @@ -230,6 +234,8 @@ impl Service { runtime.block_on( interval .for_each(move |_| { + // wait for node to process + std::thread::sleep(TIME_DELAY_FROM_SLOT); // if a non-fatal error occurs, proceed to the next slot. let _ignore_error = service.per_slot_execution(); // completed a slot process From bd860eb3e155177ca47714e6f9356d42c39eb960 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 15:30:09 +1100 Subject: [PATCH 45/57] Fixes bug in epoch processing. - Was using the wrong slot to determine relative epoch. - Added a non-related test I build during the search --- .../src/per_epoch_processing/validator_statuses.rs | 2 +- eth2/types/src/slot_epoch.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index 50f3ec3727..02149cc5a1 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -227,7 +227,7 @@ impl ValidatorStatuses { status.is_previous_epoch_attester = true; // The inclusion slot and distance are only required for previous epoch attesters. - let relative_epoch = RelativeEpoch::from_slot(state.slot, a.data.slot, spec)?; + let relative_epoch = RelativeEpoch::from_slot(state.slot, a.inclusion_slot, spec)?; status.inclusion_info = Some(InclusionInfo { slot: a.inclusion_slot, distance: inclusion_distance(a), diff --git a/eth2/types/src/slot_epoch.rs b/eth2/types/src/slot_epoch.rs index c40b6badfc..a8c32ef859 100644 --- a/eth2/types/src/slot_epoch.rs +++ b/eth2/types/src/slot_epoch.rs @@ -113,6 +113,16 @@ mod epoch_tests { all_tests!(Epoch); + #[test] + fn epoch_start_end() { + let slots_per_epoch = 8; + + let epoch = Epoch::new(0); + + assert_eq!(epoch.start_slot(slots_per_epoch), Slot::new(0)); + assert_eq!(epoch.end_slot(slots_per_epoch), Slot::new(7)); + } + #[test] fn slot_iter() { let slots_per_epoch = 8; From c6fc4f0769ae423f38cc3a46d162d79bd4425908 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 15:35:27 +1100 Subject: [PATCH 46/57] Fix bug in attestation production --- beacon_node/beacon_chain/src/beacon_chain.rs | 37 ++++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 046f37a81a..b272520c59 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -482,14 +482,37 @@ where trace!("BeaconChain::produce_attestation: shard: {}", shard); let state = self.state.read(); - let target_root = *self.state.read().get_block_root( - self.state + let current_epoch_start_slot = self + .state + .read() + .slot + .epoch(self.spec.slots_per_epoch) + .start_slot(self.spec.slots_per_epoch); + + let target_root = if state.slot == current_epoch_start_slot { + // If we're on the first slot of the state's epoch. + if self.head().beacon_block.slot == state.slot { + // If the current head block is from the current slot, use its block root. + self.head().beacon_block_root + } else { + // If the current head block is not from this slot, use the slot from the previous + // epoch. + let root = *self.state.read().get_block_root( + current_epoch_start_slot - self.spec.slots_per_epoch, + &self.spec, + )?; + + root + } + } else { + // If we're not on the first slot of the epoch. + let root = *self + .state .read() - .slot - .epoch(self.spec.slots_per_epoch) - .start_slot(self.spec.slots_per_epoch), - &self.spec, - )?; + .get_block_root(current_epoch_start_slot, &self.spec)?; + + root + }; Ok(AttestationData { slot: self.state.read().slot, From c85da612f64c16640cbcfab2c8fbc3a9d6de12f1 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 31 Mar 2019 15:35:54 +1100 Subject: [PATCH 47/57] Remove debugging statements --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/rpc/src/attestation.rs | 3 --- validator_client/src/attestation_producer/grpc.rs | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 046f37a81a..f600fb1ac4 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -290,7 +290,7 @@ where /// fork-choice rule). /// /// It is important to note that the `beacon_state` returned may not match the present slot. It - /// is the state as it was when the head block was recieved, which could be some slots prior to + /// is the state as it was when the head block was received, which could be some slots prior to /// now. pub fn head(&self) -> RwLockReadGuard { self.canonical_head.read() diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index 494b240678..abef49df1d 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -86,9 +86,6 @@ impl AttestationService for AttestationServiceInstance { } }; - dbg!("Produced attestation"); - dbg!(attestation_data.clone()); - let mut attestation_data_proto = AttestationDataProto::new(); attestation_data_proto.set_ssz(ssz_encode(&attestation_data)); diff --git a/validator_client/src/attestation_producer/grpc.rs b/validator_client/src/attestation_producer/grpc.rs index 49c577e242..900a92f321 100644 --- a/validator_client/src/attestation_producer/grpc.rs +++ b/validator_client/src/attestation_producer/grpc.rs @@ -22,8 +22,6 @@ impl BeaconNodeAttestation for AttestationServiceClient { .produce_attestation_data(&req) .map_err(|err| BeaconNodeError::RemoteFailure(format!("{:?}", err)))?; - dbg!("Produced Attestation Data"); - let (attestation_data, _index) = AttestationData::ssz_decode(reply.get_attestation_data().get_ssz(), 0) .map_err(|_| BeaconNodeError::DecodeFailure)?; From e0b5e74e7cb269b75db4ec2469bbf8fd29bfa45c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Sun, 31 Mar 2019 15:45:52 +1100 Subject: [PATCH 48/57] Removes further unneccessary debug output --- beacon_node/client/src/notifier.rs | 4 ++-- beacon_node/network/src/service.rs | 2 +- .../src/per_block_processing/validate_attestation.rs | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 91a9f3a261..1a5ecbb53b 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -22,13 +22,13 @@ pub fn run(client: &Client, executor: TaskExecutor, exit: Exi // build heartbeat logic here let heartbeat = move |_| { - debug!(log, "Temp heartbeat output"); + //debug!(log, "Temp heartbeat output"); //TODO: Remove this logic. Testing only let mut count = counter.lock().unwrap(); *count += 1; if *count % 5 == 0 { - debug!(log, "Sending Message"); + // debug!(log, "Sending Message"); network.send_message(); } diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index b2d2b5a246..aee7eb4660 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -161,7 +161,7 @@ fn network_service( libp2p_service.swarm.send_rpc(peer_id, rpc_event); } OutgoingMessage::NotifierTest => { - debug!(log, "Received message from notifier"); + // debug!(log, "Received message from notifier"); } }; } diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 76b4157968..3b89bec99c 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -99,9 +99,6 @@ fn validate_attestation_parametric( crosslink_data_root: attestation.data.crosslink_data_root, epoch: attestation.data.slot.epoch(spec.slots_per_epoch), }; - dbg!(attestation.clone()); - dbg!(state.latest_crosslinks[attestation.data.shard as usize].clone()); - dbg!(potential_crosslink.clone()); verify!( (attestation.data.previous_crosslink == state.latest_crosslinks[attestation.data.shard as usize]) From c596e3f7d77fb04b7881b012019f66e5bf55b213 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 17:26:28 +1100 Subject: [PATCH 49/57] Change log levels of gossipsub events --- beacon_node/eth2-libp2p/src/behaviour.rs | 4 ++-- beacon_node/eth2-libp2p/src/service.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/behaviour.rs b/beacon_node/eth2-libp2p/src/behaviour.rs index fdf12f0bff..88bfd0042a 100644 --- a/beacon_node/eth2-libp2p/src/behaviour.rs +++ b/beacon_node/eth2-libp2p/src/behaviour.rs @@ -12,7 +12,7 @@ use libp2p::{ tokio_io::{AsyncRead, AsyncWrite}, NetworkBehaviour, PeerId, }; -use slog::{debug, o, warn}; +use slog::{debug, o, trace, warn}; use ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream}; use types::{Attestation, BeaconBlock}; use types::{Topic, TopicHash}; @@ -47,7 +47,7 @@ impl NetworkBehaviourEventProcess { - debug!(self.log, "Received GossipEvent"; "msg" => format!("{:?}", gs_msg)); + trace!(self.log, "Received GossipEvent"; "msg" => format!("{:?}", gs_msg)); let pubsub_message = match PubsubMessage::ssz_decode(&gs_msg.data, 0) { //TODO: Punish peer on error diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 0dc30cf420..f52d11ef1e 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -113,7 +113,7 @@ impl Stream for Service { topics, message, } => { - debug!(self.log, "Pubsub message received: {:?}", message); + trace!(self.log, "Pubsub message received: {:?}", message); return Ok(Async::Ready(Some(Libp2pEvent::PubsubMessage { source, topics, From a93f89894627a0b1276617965940abf91672b2db Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 31 Mar 2019 17:27:04 +1100 Subject: [PATCH 50/57] Improve gossipsub block processing --- beacon_node/network/src/beacon_chain.rs | 2 +- beacon_node/network/src/message_handler.rs | 5 +- beacon_node/network/src/sync/import_queue.rs | 38 ++++- beacon_node/network/src/sync/simple_sync.rs | 158 +++++++++++++----- .../testing_beacon_state_builder.rs | 2 +- 5 files changed, 160 insertions(+), 45 deletions(-) diff --git a/beacon_node/network/src/beacon_chain.rs b/beacon_node/network/src/beacon_chain.rs index 7a8efb2540..827adeb3c9 100644 --- a/beacon_node/network/src/beacon_chain.rs +++ b/beacon_node/network/src/beacon_chain.rs @@ -10,7 +10,7 @@ use beacon_chain::{ use eth2_libp2p::rpc::HelloMessage; use types::{Attestation, BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; -pub use beacon_chain::{BeaconChainError, BlockProcessingOutcome}; +pub use beacon_chain::{BeaconChainError, BlockProcessingOutcome, InvalidBlock}; /// The network's API to the beacon chain. pub trait BeaconChain: Send + Sync { diff --git a/beacon_node/network/src/message_handler.rs b/beacon_node/network/src/message_handler.rs index 0efa6b96fd..098a5b4bfb 100644 --- a/beacon_node/network/src/message_handler.rs +++ b/beacon_node/network/src/message_handler.rs @@ -208,8 +208,9 @@ impl MessageHandler { fn handle_gossip(&mut self, peer_id: PeerId, gossip_message: PubsubMessage) { match gossip_message { PubsubMessage::Block(message) => { - self.sync - .on_block_gossip(peer_id, message, &mut self.network_context) + let _should_foward_on = + self.sync + .on_block_gossip(peer_id, message, &mut self.network_context); } PubsubMessage::Attestation(message) => { self.sync diff --git a/beacon_node/network/src/sync/import_queue.rs b/beacon_node/network/src/sync/import_queue.rs index b9280440bf..0026347eb2 100644 --- a/beacon_node/network/src/sync/import_queue.rs +++ b/beacon_node/network/src/sync/import_queue.rs @@ -104,7 +104,7 @@ impl ImportQueue { } /// Returns `true` if `self.chain` has not yet processed this block. - pub fn is_new_block(&self, block_root: &Hash256) -> bool { + pub fn chain_has_not_seen_block(&self, block_root: &Hash256) -> bool { self.chain .is_new_block_root(&block_root) .unwrap_or_else(|_| { @@ -125,7 +125,7 @@ impl ImportQueue { let new_roots: Vec = block_roots .iter() // Ignore any roots already processed by the chain. - .filter(|brs| self.is_new_block(&brs.block_root)) + .filter(|brs| self.chain_has_not_seen_block(&brs.block_root)) // Ignore any roots already stored in the queue. .filter(|brs| !self.partials.iter().any(|p| p.block_root == brs.block_root)) .cloned() @@ -168,7 +168,7 @@ impl ImportQueue { for header in headers { let block_root = Hash256::from_slice(&header.hash_tree_root()[..]); - if self.is_new_block(&block_root) { + if self.chain_has_not_seen_block(&block_root) { self.insert_header(block_root, header, sender.clone()); required_bodies.push(block_root) } @@ -186,6 +186,12 @@ impl ImportQueue { } } + pub fn enqueue_full_blocks(&mut self, blocks: Vec, sender: PeerId) { + for block in blocks { + self.insert_full_block(block, sender.clone()); + } + } + /// Inserts a header to the queue. /// /// If the header already exists, the `inserted` time is set to `now` and not other @@ -239,6 +245,32 @@ impl ImportQueue { } }); } + + /// Updates an existing `partial` with the completed block, or adds a new (complete) partial. + /// + /// If the partial already existed, the `inserted` time is set to `now`. + fn insert_full_block(&mut self, block: BeaconBlock, sender: PeerId) { + let block_root = Hash256::from_slice(&block.hash_tree_root()[..]); + + let partial = PartialBeaconBlock { + slot: block.slot, + block_root, + header: Some(block.block_header()), + body: Some(block.body), + inserted: Instant::now(), + sender, + }; + + if let Some(i) = self + .partials + .iter() + .position(|p| p.block_root == block_root) + { + self.partials[i] = partial; + } else { + self.partials.push(partial) + } + } } /// Individual components of a `BeaconBlock`, potentially all that are required to form a full diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index e8a3da656d..6a78dc57d8 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -1,10 +1,11 @@ use super::import_queue::ImportQueue; -use crate::beacon_chain::BeaconChain; +use crate::beacon_chain::{BeaconChain, BlockProcessingOutcome, InvalidBlock}; use crate::message_handler::NetworkContext; use eth2_libp2p::rpc::methods::*; use eth2_libp2p::rpc::{RPCRequest, RPCResponse, RequestId}; use eth2_libp2p::PeerId; use slog::{debug, error, info, o, warn}; +use ssz::TreeHash; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -16,6 +17,10 @@ const SLOT_IMPORT_TOLERANCE: u64 = 100; /// The amount of seconds a block (or partial block) may exist in the import queue. const QUEUE_STALE_SECS: u64 = 60; +/// If a block is more than `FUTURE_SLOT_TOLERANCE` slots ahead of our slot clock, we drop it. +/// Otherwise we queue it. +const FUTURE_SLOT_TOLERANCE: u64 = 1; + /// Keeps track of syncing information for known connected peers. #[derive(Clone, Copy, Debug)] pub struct PeerSyncInfo { @@ -536,65 +541,130 @@ impl SimpleSync { } /// Process a gossip message declaring a new block. + /// + /// Returns a `bool` which, if `true`, indicates we should forward the block to our peers. pub fn on_block_gossip( &mut self, peer_id: PeerId, block: BeaconBlock, network: &mut NetworkContext, - ) { + ) -> bool { info!( self.log, "NewGossipBlock"; "peer" => format!("{:?}", peer_id), ); - /* // Ignore any block from a finalized slot. - if self.slot_is_finalized(msg.slot) { + if self.slot_is_finalized(block.slot) { warn!( self.log, "NewGossipBlock"; "msg" => "new block slot is finalized.", - "slot" => msg.slot, + "block_slot" => block.slot, ); - return; + return false; } + let block_root = Hash256::from_slice(&block.hash_tree_root()); + // Ignore any block that the chain already knows about. - if self.chain_has_seen_block(&msg.block_root) { - return; + if self.chain_has_seen_block(&block_root) { + println!("this happened"); + // TODO: Age confirm that we shouldn't forward a block if we already know of it. + return false; } - // k - if msg.slot == self.chain.hello_message().best_slot + 1 { - self.request_block_headers( - peer_id, - BeaconBlockHeadersRequest { - start_root: msg.block_root, - start_slot: msg.slot, - max_headers: 1, - skip_slots: 0, - }, - network, - ) + debug!( + self.log, + "NewGossipBlock"; + "peer" => format!("{:?}", peer_id), + "msg" => "processing block", + ); + match self.chain.process_block(block.clone()) { + Ok(BlockProcessingOutcome::InvalidBlock(InvalidBlock::ParentUnknown)) => { + // get the parent. + true + } + Ok(BlockProcessingOutcome::InvalidBlock(InvalidBlock::FutureSlot { + present_slot, + block_slot, + })) => { + if block_slot - present_slot > FUTURE_SLOT_TOLERANCE { + // The block is too far in the future, drop it. + warn!( + self.log, "NewGossipBlock"; + "msg" => "future block rejected", + "present_slot" => present_slot, + "block_slot" => block_slot, + "FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE, + "peer" => format!("{:?}", peer_id), + ); + // Do not forward the block around to peers. + false + } else { + // The block is in the future, but not too far. + warn!( + self.log, "NewGossipBlock"; + "msg" => "queuing future block", + "present_slot" => present_slot, + "block_slot" => block_slot, + "FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE, + "peer" => format!("{:?}", peer_id), + ); + // Queue the block for later processing. + self.import_queue.enqueue_full_blocks(vec![block], peer_id); + // Forward the block around to peers. + true + } + } + Ok(outcome) => { + if outcome.is_invalid() { + // The peer has sent a block which is fundamentally invalid. + warn!( + self.log, "NewGossipBlock"; + "msg" => "invalid block from peer", + "outcome" => format!("{:?}", outcome), + "peer" => format!("{:?}", peer_id), + ); + // Disconnect the peer + network.disconnect(peer_id, GoodbyeReason::Fault); + // Do not forward the block to peers. + false + } else if outcome.sucessfully_processed() { + // The block was valid and we processed it successfully. + info!( + self.log, "NewGossipBlock"; + "msg" => "block import successful", + "peer" => format!("{:?}", peer_id), + ); + // Forward the block to peers + true + } else { + // The block wasn't necessarily invalid but we didn't process it successfully. + // This condition shouldn't be reached. + error!( + self.log, "NewGossipBlock"; + "msg" => "unexpected condition in processing block.", + "outcome" => format!("{:?}", outcome), + ); + // Do not forward the block on. + false + } + } + Err(e) => { + // We encountered an error whilst processing the block. + // + // Blocks should not be able to trigger errors, instead they should be flagged as + // invalid. + error!( + self.log, "NewGossipBlock"; + "msg" => "internal error in processing block.", + "error" => format!("{:?}", e), + ); + // Do not forward the block to peers. + false + } } - - // TODO: if the block is a few more slots ahead, try to get all block roots from then until - // now. - // - // Note: only requests the new block -- will fail if we don't have its parents. - if !self.chain_has_seen_block(&msg.block_root) { - self.request_block_headers( - peer_id, - BeaconBlockHeadersRequest { - start_root: msg.block_root, - start_slot: msg.slot, - max_headers: 1, - skip_slots: 0, - }, - network, - ) - } - */ } /// Process a gossip message declaring a new attestation. @@ -724,6 +794,18 @@ impl SimpleSync { network.send_rpc_request(peer_id.clone(), RPCRequest::BeaconBlockBodies(req)); } + /// Returns `true` if `self.chain` has not yet processed this block. + pub fn chain_has_seen_block(&self, block_root: &Hash256) -> bool { + !self + .chain + .is_new_block_root(&block_root) + .unwrap_or_else(|_| { + error!(self.log, "Unable to determine if block is new."); + false + }) + } + + /// Returns `true` if the given slot is finalized in our chain. fn slot_is_finalized(&self, slot: Slot) -> bool { slot <= self .chain diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index f437240dc6..e25da37e7e 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -120,7 +120,7 @@ impl TestingBeaconStateBuilder { }) .collect(); - let genesis_time = 1553977336; // arbitrary + let genesis_time = 1554013000; // arbitrary let mut state = BeaconState::genesis( genesis_time, From 170f993032002f0d50fdb8bed25564fbad678622 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 1 Apr 2019 12:14:23 +1100 Subject: [PATCH 51/57] Clean up warnings --- beacon_node/client/src/lib.rs | 2 -- beacon_node/eth2-libp2p/src/rpc/methods.rs | 2 +- beacon_node/rpc/src/validator.rs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 807fd9301e..6b4277c264 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -15,13 +15,11 @@ use futures::{future::Future, Stream}; use network::Service as NetworkService; use slog::{error, info, o}; use slot_clock::SlotClock; -use ssz::TreeHash; use std::marker::PhantomData; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::runtime::TaskExecutor; use tokio::timer::Interval; -use types::Hash256; /// Main beacon node client service. This provides the connection and initialisation of the clients /// sub-services in multiple threads. diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index f9adb93c17..dc0be19a92 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -1,7 +1,7 @@ use ssz::{Decodable, DecodeError, Encodable, SszStream}; /// Available RPC methods types and ids. use ssz_derive::{Decode, Encode}; -use types::{Attestation, BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; +use types::{BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; #[derive(Debug)] /// Available Serenity Libp2p RPC methods diff --git a/beacon_node/rpc/src/validator.rs b/beacon_node/rpc/src/validator.rs index 0a9d7015c9..4511a8913e 100644 --- a/beacon_node/rpc/src/validator.rs +++ b/beacon_node/rpc/src/validator.rs @@ -83,7 +83,7 @@ impl ValidatorService for ValidatorServiceInstance { RpcStatusCode::InvalidArgument, Some("Invalid public_key".to_string()), )) - .map_err(move |e| warn!(log_clone, "failed to reply {:?}", req)); + .map_err(move |_| warn!(log_clone, "failed to reply {:?}", req)); return ctx.spawn(f); } }; From 9f8850d0a18bbdefd9fc5d8eefadb42dc8bbb327 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 1 Apr 2019 12:14:44 +1100 Subject: [PATCH 52/57] Adds bootnode CLI parameter --- beacon_node/client/src/client_config.rs | 12 ++++++++++++ beacon_node/src/main.rs | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index cad287f2cc..f7a257a3a9 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; use types::multiaddr::Protocol; use types::multiaddr::ToMultiaddr; use types::ChainSpec; +use types::Multiaddr; /// Stores the client configuration for this Lighthouse instance. #[derive(Debug, Clone)] @@ -88,6 +89,17 @@ impl ClientConfig { } } + // Custom bootnodes + // TODO: Handle list of addresses + if let Some(boot_addresses_str) = args.value_of("boot_nodes") { + if let Ok(boot_address) = boot_addresses_str.parse::() { + config.net_conf.boot_nodes.append(&mut vec![boot_address]); + } else { + error!(log, "Invalid Bootnode multiaddress"; "Multiaddr" => boot_addresses_str); + return Err("Invalid IP Address"); + } + } + /* Filesystem related arguments */ // Custom datadir diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index ea74c73766..a4e1ee1308 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -16,6 +16,7 @@ fn main() { .version(version::version().as_str()) .author("Sigma Prime ") .about("Eth 2.0 Client") + // file system related arguments .arg( Arg::with_name("datadir") .long("datadir") @@ -23,6 +24,7 @@ fn main() { .help("Data directory for keys and databases.") .takes_value(true), ) + // network related arguments .arg( Arg::with_name("listen_address") .long("listen-address") @@ -37,6 +39,14 @@ fn main() { .help("Network listen port for p2p connections.") .takes_value(true), ) + .arg( + Arg::with_name("boot-nodes") + .long("boot-nodes") + .value_name("BOOTNODES") + .help("A list of comma separated multi addresses representing bootnodes to connect to.") + .takes_value(true), + ) + // rpc related arguments .arg( Arg::with_name("rpc") .long("rpc") From 111c81f42836f215a4e6190cd95f139c83328b8c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 1 Apr 2019 15:23:38 +1100 Subject: [PATCH 53/57] Add ParentUnknown block processing to `SimpleSync` --- beacon_node/network/src/sync/simple_sync.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 6a78dc57d8..e7ef301bb7 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -582,7 +582,23 @@ impl SimpleSync { ); match self.chain.process_block(block.clone()) { Ok(BlockProcessingOutcome::InvalidBlock(InvalidBlock::ParentUnknown)) => { - // get the parent. + // The block was valid and we processed it successfully. + debug!( + self.log, "NewGossipBlock"; + "msg" => "parent block unknown", + "parent_root" => format!("{}", block.previous_block_root), + "peer" => format!("{:?}", peer_id), + ); + // Send a hello to learn of the clients best slot so we can then sync the require + // parent(s). + network.send_rpc_request( + peer_id.clone(), + RPCRequest::Hello(self.chain.hello_message()), + ); + // Forward the block onto our peers. + // + // Note: this may need to be changed if we decide to only forward blocks if we have + // all required info. true } Ok(BlockProcessingOutcome::InvalidBlock(InvalidBlock::FutureSlot { From a3ca3ec50d1058a45368918fe8313cabadf3bb72 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 1 Apr 2019 15:32:04 +1100 Subject: [PATCH 54/57] Break block proc. loop if we get a bad block --- beacon_node/network/src/sync/simple_sync.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index e7ef301bb7..841ae37ac5 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -726,6 +726,7 @@ impl SimpleSync { "reason" => format!("{:?}", outcome), ); network.disconnect(sender, GoodbyeReason::Fault); + break; } // If this results to true, the item will be removed from the queue. From a7df4f180081aa26a58bc3c4098f5365bce9f26c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 1 Apr 2019 15:38:22 +1100 Subject: [PATCH 55/57] Add log when block not processed in queue --- beacon_node/network/src/sync/simple_sync.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 841ae37ac5..038975f05a 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -733,6 +733,14 @@ impl SimpleSync { if outcome.sucessfully_processed() { successful += 1; self.import_queue.remove(block_root); + } else { + debug!( + self.log, + "ProcessImportQueue"; + "msg" => "Block not imported", + "outcome" => format!("{:?}", outcome), + "peer" => format!("{:?}", sender), + ); } } Err(e) => { From 5e80b90301d1abab663180d7d13278987b6597e1 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 1 Apr 2019 15:51:48 +1100 Subject: [PATCH 56/57] Extend queue stale time, queue more blocks --- beacon_node/network/src/sync/simple_sync.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 038975f05a..9a1e51bdd4 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -15,7 +15,7 @@ use types::{Attestation, BeaconBlock, Epoch, Hash256, Slot}; const SLOT_IMPORT_TOLERANCE: u64 = 100; /// The amount of seconds a block (or partial block) may exist in the import queue. -const QUEUE_STALE_SECS: u64 = 60; +const QUEUE_STALE_SECS: u64 = 600; /// If a block is more than `FUTURE_SLOT_TOLERANCE` slots ahead of our slot clock, we drop it. /// Otherwise we queue it. @@ -589,6 +589,9 @@ impl SimpleSync { "parent_root" => format!("{}", block.previous_block_root), "peer" => format!("{:?}", peer_id), ); + // Queue the block for later processing. + self.import_queue + .enqueue_full_blocks(vec![block], peer_id.clone()); // Send a hello to learn of the clients best slot so we can then sync the require // parent(s). network.send_rpc_request( From 3f160d3b995f848c035f1ab7c5d46da3dcc208c2 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 1 Apr 2019 16:29:11 +1100 Subject: [PATCH 57/57] Correct bootnodes cli parameter --- beacon_node/client/src/client_config.rs | 4 ++-- beacon_node/src/main.rs | 2 +- .../src/test_utils/testing_beacon_state_builder.rs | 11 ++++++++--- validator_client/src/service.rs | 12 +++++++----- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/beacon_node/client/src/client_config.rs b/beacon_node/client/src/client_config.rs index f7a257a3a9..407171ff58 100644 --- a/beacon_node/client/src/client_config.rs +++ b/beacon_node/client/src/client_config.rs @@ -77,7 +77,7 @@ impl ClientConfig { } // Custom listening address ipv4/ipv6 // TODO: Handle list of addresses - if let Some(listen_address_str) = args.value_of("listen_address") { + if let Some(listen_address_str) = args.value_of("listen-address") { if let Ok(listen_address) = listen_address_str.parse::() { let multiaddr = SocketAddr::new(listen_address, config.net_conf.listen_port) .to_multiaddr() @@ -91,7 +91,7 @@ impl ClientConfig { // Custom bootnodes // TODO: Handle list of addresses - if let Some(boot_addresses_str) = args.value_of("boot_nodes") { + if let Some(boot_addresses_str) = args.value_of("boot-nodes") { if let Ok(boot_address) = boot_addresses_str.parse::() { config.net_conf.boot_nodes.append(&mut vec![boot_address]); } else { diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index a4e1ee1308..45aafb3ce5 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -26,7 +26,7 @@ fn main() { ) // network related arguments .arg( - Arg::with_name("listen_address") + Arg::with_name("listen-address") .long("listen-address") .value_name("Listen Address") .help("The Network address to listen for p2p connections.") diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index def58b0d74..445debae7c 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -122,12 +122,17 @@ impl TestingBeaconStateBuilder { }) .collect(); + // TODO: Testing only. Burn with fire later. + // set genesis to the last 30 minute block. + // this is used for testing only. Allows multiple nodes to connect within a 30min window + // and agree on a genesis let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() - .as_secs() - - 30; - let genesis_time = now; // arbitrary + .as_secs(); + let secs_after_last_period = now.checked_rem(30 * 60).unwrap_or(0); + // genesis is now the last 30 minute block. + let genesis_time = now - secs_after_last_period; let mut state = BeaconState::genesis( genesis_time, diff --git a/validator_client/src/service.rs b/validator_client/src/service.rs index 38883e68fa..ce19c23e93 100644 --- a/validator_client/src/service.rs +++ b/validator_client/src/service.rs @@ -292,11 +292,13 @@ impl Service { let current_epoch = self.current_slot.epoch(self.spec.slots_per_epoch); // spawn a new thread separate to the runtime // TODO: Handle thread termination/timeout - std::thread::spawn(move || { - // the return value is a future which returns ready. - // built to be compatible with the tokio runtime. - let _empty = cloned_manager.run_update(current_epoch, cloned_log.clone()); - }); + // TODO: Add duties thread back in, with channel to process duties in duty change. + // leave sequential for now. + //std::thread::spawn(move || { + // the return value is a future which returns ready. + // built to be compatible with the tokio runtime. + let _empty = cloned_manager.run_update(current_epoch, cloned_log.clone()); + //}); } /// If there are any duties to process, spawn a separate thread and perform required actions.