Optimize validator duties (#2243)

## Issue Addressed

Closes #2052

## Proposed Changes

- Refactor the attester/proposer duties endpoints in the BN
    - Performance improvements
    - Fixes some potential inconsistencies with the dependent root fields.
    - Removes `http_api::beacon_proposer_cache` and just uses the one on the `BeaconChain` instead.
    - Move the code for the proposer/attester duties endpoints into separate files, for readability.
- Refactor the `DutiesService` in the VC
    - Required to reduce the delay on broadcasting new blocks.
    - Gets rid of the `ValidatorDuty` shim struct that came about when we adopted the standard API.
    - Separate block/attestation duty tasks so that they don't block each other when one is slow.
- In the VC, use `PublicKeyBytes` to represent validators instead of `PublicKey`. `PublicKey` is a legit crypto object whilst `PublicKeyBytes` is just a byte-array, it's much faster to clone/hash `PublicKeyBytes` and this change has had a significant impact on runtimes.
    - Unfortunately this has created lots of dust changes.
 - In the BN, store `PublicKeyBytes` in the `beacon_proposer_cache` and allow access to them. The HTTP API always sends `PublicKeyBytes` over the wire and the conversion from `PublicKey` -> `PublickeyBytes` is non-trivial, especially when queries have 100s/1000s of validators (like Pyrmont).
 - Add the `state_processing::state_advance` mod which dedups a lot of the "apply `n` skip slots to the state" code.
    - This also fixes a bug with some functions which were failing to include a state root as per [this comment](072695284f/consensus/state_processing/src/state_advance.rs (L69-L74)). I couldn't find any instance of this bug that resulted in anything more severe than keying a shuffling cache by the wrong block root.
 - Swap the VC block service to use `mpsc` from `tokio` instead of `futures`. This is consistent with the rest of the code base.
    
~~This PR *reduces* the size of the codebase 🎉~~ It *used* to reduce the size of the code base before I added more comments. 

## Observations on Prymont

- Proposer duties times down from peaks of 450ms to consistent <1ms.
- Current epoch attester duties times down from >1s peaks to a consistent 20-30ms.
- Block production down from +600ms to 100-200ms.

## Additional Info

- ~~Blocked on #2241~~
- ~~Blocked on #2234~~

## TODO

- [x] ~~Refactor this into some smaller PRs?~~ Leaving this as-is for now.
- [x] Address `per_slot_processing` roots.
- [x] Investigate slow next epoch times. Not getting added to cache on block processing?
- [x] Consider [this](072695284f/beacon_node/store/src/hot_cold_store.rs (L811-L812)) in the scenario of replacing the state roots


Co-authored-by: pawan <pawandhananjay@gmail.com>
Co-authored-by: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Paul Hauner
2021-03-17 05:09:57 +00:00
parent 6a69b20be1
commit 015ab7d0a7
49 changed files with 2201 additions and 1833 deletions

View File

@@ -21,6 +21,7 @@ eth2_interop_keypairs = { path = "../common/eth2_interop_keypairs" }
slashing_protection = { path = "./slashing_protection" }
slot_clock = { path = "../common/slot_clock" }
types = { path = "../consensus/types" }
safe_arith = { path = "../consensus/safe_arith" }
serde = "1.0.116"
serde_derive = "1.0.116"
bincode = "1.3.1"

View File

@@ -1,6 +1,6 @@
use serde_derive::{Deserialize, Serialize};
use std::collections::HashSet;
use types::{Epoch, Hash256, PublicKey, Slot};
use types::{Epoch, Hash256, PublicKeyBytes, Slot};
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
@@ -13,7 +13,7 @@ pub struct InterchangeMetadata {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct InterchangeData {
pub pubkey: PublicKey,
pub pubkey: PublicKeyBytes,
pub signed_blocks: Vec<SignedBlock>,
pub signed_attestations: Vec<SignedAttestation>,
}

View File

@@ -5,7 +5,7 @@ use crate::{
};
use serde_derive::{Deserialize, Serialize};
use tempfile::tempdir;
use types::{Epoch, Hash256, PublicKey, Slot};
use types::{Epoch, Hash256, PublicKeyBytes, Slot};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MultiTestCase {
@@ -25,7 +25,7 @@ pub struct TestCase {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TestBlock {
pub pubkey: PublicKey,
pub pubkey: PublicKeyBytes,
pub slot: Slot,
pub signing_root: Hash256,
pub should_succeed: bool,
@@ -33,7 +33,7 @@ pub struct TestBlock {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TestAttestation {
pub pubkey: PublicKey,
pub pubkey: PublicKeyBytes,
pub source_epoch: Epoch,
pub target_epoch: Epoch,
pub signing_root: Hash256,

View File

@@ -17,7 +17,7 @@ pub use crate::slashing_database::{
use rusqlite::Error as SQLError;
use std::io::{Error as IOError, ErrorKind};
use std::string::ToString;
use types::{Hash256, PublicKey};
use types::{Hash256, PublicKeyBytes};
/// The filename within the `validators` directory that contains the slashing protection DB.
pub const SLASHING_PROTECTION_FILENAME: &str = "slashing_protection.sqlite";
@@ -27,7 +27,7 @@ pub const SLASHING_PROTECTION_FILENAME: &str = "slashing_protection.sqlite";
/// This could be because it's slashable, or because an error occurred.
#[derive(PartialEq, Debug)]
pub enum NotSafe {
UnregisteredValidator(PublicKey),
UnregisteredValidator(PublicKeyBytes),
InvalidBlock(InvalidBlock),
InvalidAttestation(InvalidAttestation),
IOError(ErrorKind),

View File

@@ -16,7 +16,7 @@ fn block_same_slot() {
let pk = pubkey(0);
slashing_db.register_validator(&pk).unwrap();
slashing_db.register_validator(pk).unwrap();
// A stream of blocks all with the same slot.
let num_blocks = 10;
@@ -37,7 +37,7 @@ fn attestation_same_target() {
let pk = pubkey(0);
slashing_db.register_validator(&pk).unwrap();
slashing_db.register_validator(pk).unwrap();
// A stream of attestations all with the same target.
let num_attestations = 10;
@@ -64,7 +64,7 @@ fn attestation_surround_fest() {
let pk = pubkey(0);
slashing_db.register_validator(&pk).unwrap();
slashing_db.register_validator(pk).unwrap();
// A stream of attestations that all surround each other.
let num_attestations = 10;

View File

@@ -10,7 +10,7 @@ use rusqlite::{params, OptionalExtension, Transaction, TransactionBehavior};
use std::fs::{File, OpenOptions};
use std::path::Path;
use std::time::Duration;
use types::{AttestationData, BeaconBlockHeader, Epoch, Hash256, PublicKey, SignedRoot, Slot};
use types::{AttestationData, BeaconBlockHeader, Epoch, Hash256, PublicKeyBytes, SignedRoot, Slot};
type Pool = r2d2::Pool<SqliteConnectionManager>;
@@ -147,14 +147,14 @@ impl SlashingDatabase {
///
/// This allows the validator to record their signatures in the database, and check
/// for slashings.
pub fn register_validator(&self, validator_pk: &PublicKey) -> Result<(), NotSafe> {
self.register_validators(std::iter::once(validator_pk))
pub fn register_validator(&self, validator_pk: PublicKeyBytes) -> Result<(), NotSafe> {
self.register_validators(std::iter::once(&validator_pk))
}
/// Register multiple validators with the slashing protection database.
pub fn register_validators<'a>(
&self,
public_keys: impl Iterator<Item = &'a PublicKey>,
public_keys: impl Iterator<Item = &'a PublicKeyBytes>,
) -> Result<(), NotSafe> {
let mut conn = self.conn_pool.get()?;
let txn = conn.transaction()?;
@@ -168,7 +168,7 @@ impl SlashingDatabase {
/// The caller must commit the transaction for the changes to be persisted.
pub fn register_validators_in_txn<'a>(
&self,
public_keys: impl Iterator<Item = &'a PublicKey>,
public_keys: impl Iterator<Item = &'a PublicKeyBytes>,
txn: &Transaction,
) -> Result<(), NotSafe> {
let mut stmt = txn.prepare("INSERT INTO validators (public_key) VALUES (?1)")?;
@@ -183,7 +183,7 @@ impl SlashingDatabase {
/// Check that all of the given validators are registered.
pub fn check_validator_registrations<'a>(
&self,
mut public_keys: impl Iterator<Item = &'a PublicKey>,
mut public_keys: impl Iterator<Item = &'a PublicKeyBytes>,
) -> Result<(), NotSafe> {
let mut conn = self.conn_pool.get()?;
let txn = conn.transaction()?;
@@ -195,7 +195,7 @@ impl SlashingDatabase {
///
/// This is NOT the same as a validator index, and depends on the ordering that validators
/// are registered with the slashing protection database (and may vary between machines).
pub fn get_validator_id(&self, public_key: &PublicKey) -> Result<i64, NotSafe> {
pub fn get_validator_id(&self, public_key: &PublicKeyBytes) -> Result<i64, NotSafe> {
let mut conn = self.conn_pool.get()?;
let txn = conn.transaction()?;
self.get_validator_id_in_txn(&txn, public_key)
@@ -204,17 +204,17 @@ impl SlashingDatabase {
fn get_validator_id_in_txn(
&self,
txn: &Transaction,
public_key: &PublicKey,
public_key: &PublicKeyBytes,
) -> Result<i64, NotSafe> {
self.get_validator_id_opt(txn, public_key)?
.ok_or_else(|| NotSafe::UnregisteredValidator(public_key.clone()))
.ok_or_else(|| NotSafe::UnregisteredValidator(*public_key))
}
/// Optional version of `get_validator_id`.
fn get_validator_id_opt(
&self,
txn: &Transaction,
public_key: &PublicKey,
public_key: &PublicKeyBytes,
) -> Result<Option<i64>, NotSafe> {
Ok(txn
.query_row(
@@ -229,7 +229,7 @@ impl SlashingDatabase {
fn check_block_proposal(
&self,
txn: &Transaction,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
slot: Slot,
signing_root: SigningRoot,
) -> Result<Safe, NotSafe> {
@@ -278,7 +278,7 @@ impl SlashingDatabase {
fn check_attestation(
&self,
txn: &Transaction,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
att_source_epoch: Epoch,
att_target_epoch: Epoch,
att_signing_root: SigningRoot,
@@ -408,7 +408,7 @@ impl SlashingDatabase {
fn insert_block_proposal(
&self,
txn: &Transaction,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
slot: Slot,
signing_root: SigningRoot,
) -> Result<(), NotSafe> {
@@ -429,7 +429,7 @@ impl SlashingDatabase {
fn insert_attestation(
&self,
txn: &Transaction,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
att_source_epoch: Epoch,
att_target_epoch: Epoch,
att_signing_root: SigningRoot,
@@ -457,7 +457,7 @@ impl SlashingDatabase {
/// This is the safe, externally-callable interface for checking block proposals.
pub fn check_and_insert_block_proposal(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
block_header: &BeaconBlockHeader,
domain: Hash256,
) -> Result<Safe, NotSafe> {
@@ -471,7 +471,7 @@ impl SlashingDatabase {
/// As for `check_and_insert_block_proposal` but without requiring the whole `BeaconBlockHeader`.
pub fn check_and_insert_block_signing_root(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
slot: Slot,
signing_root: SigningRoot,
) -> Result<Safe, NotSafe> {
@@ -490,7 +490,7 @@ impl SlashingDatabase {
/// Transactional variant of `check_and_insert_block_signing_root`.
pub fn check_and_insert_block_signing_root_txn(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
slot: Slot,
signing_root: SigningRoot,
txn: &Transaction,
@@ -511,7 +511,7 @@ impl SlashingDatabase {
/// This is the safe, externally-callable interface for checking attestations.
pub fn check_and_insert_attestation(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
attestation: &AttestationData,
domain: Hash256,
) -> Result<Safe, NotSafe> {
@@ -527,7 +527,7 @@ impl SlashingDatabase {
/// As for `check_and_insert_attestation` but without requiring the whole `AttestationData`.
pub fn check_and_insert_attestation_signing_root(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
att_source_epoch: Epoch,
att_target_epoch: Epoch,
att_signing_root: SigningRoot,
@@ -548,7 +548,7 @@ impl SlashingDatabase {
/// Transactional variant of `check_and_insert_attestation_signing_root`.
fn check_and_insert_attestation_signing_root_txn(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
att_source_epoch: Epoch,
att_target_epoch: Epoch,
att_signing_root: SigningRoot,
@@ -600,7 +600,7 @@ impl SlashingDatabase {
let mut import_outcomes = vec![];
for record in interchange.data {
let pubkey = record.pubkey.clone();
let pubkey = record.pubkey;
let txn = conn.transaction()?;
match self.import_interchange_record(record, &txn) {
Ok(summary) => {
@@ -757,7 +757,7 @@ impl SlashingDatabase {
/// Remove all blocks for `public_key` with slots less than `new_min_slot`.
fn prune_signed_blocks(
&self,
public_key: &PublicKey,
public_key: &PublicKeyBytes,
new_min_slot: Slot,
txn: &Transaction,
) -> Result<(), NotSafe> {
@@ -780,7 +780,7 @@ impl SlashingDatabase {
/// Prune the signed blocks table for the given public keys.
pub fn prune_all_signed_blocks<'a>(
&self,
mut public_keys: impl Iterator<Item = &'a PublicKey>,
mut public_keys: impl Iterator<Item = &'a PublicKeyBytes>,
new_min_slot: Slot,
) -> Result<(), NotSafe> {
let mut conn = self.conn_pool.get()?;
@@ -803,7 +803,7 @@ impl SlashingDatabase {
/// attestations in the database.
fn prune_signed_attestations(
&self,
public_key: &PublicKey,
public_key: &PublicKeyBytes,
new_min_target: Epoch,
txn: &Transaction,
) -> Result<(), NotSafe> {
@@ -830,7 +830,7 @@ impl SlashingDatabase {
/// Prune the signed attestations table for the given validator keys.
pub fn prune_all_signed_attestations<'a>(
&self,
mut public_keys: impl Iterator<Item = &'a PublicKey>,
mut public_keys: impl Iterator<Item = &'a PublicKeyBytes>,
new_min_target: Epoch,
) -> Result<(), NotSafe> {
let mut conn = self.conn_pool.get()?;
@@ -853,7 +853,7 @@ impl SlashingDatabase {
/// Get a summary of a validator's slashing protection data for consumption by the user.
pub fn validator_summary(
&self,
public_key: &PublicKey,
public_key: &PublicKeyBytes,
txn: &Transaction,
) -> Result<ValidatorSummary, NotSafe> {
let validator_id = self.get_validator_id_in_txn(txn, public_key)?;
@@ -906,11 +906,11 @@ pub struct ValidatorSummary {
#[derive(Debug)]
pub enum InterchangeImportOutcome {
Success {
pubkey: PublicKey,
pubkey: PublicKeyBytes,
summary: ValidatorSummary,
},
Failure {
pubkey: PublicKey,
pubkey: PublicKeyBytes,
error: NotSafe,
},
}
@@ -981,7 +981,7 @@ mod tests {
let _db1 = SlashingDatabase::create(&file).unwrap();
let db2 = SlashingDatabase::open(&file).unwrap();
db2.register_validator(&pubkey(0)).unwrap_err();
db2.register_validator(pubkey(0)).unwrap_err();
}
// Attempting to create the same database twice should error.

View File

@@ -2,18 +2,19 @@ use crate::*;
use tempfile::{tempdir, TempDir};
use types::{
test_utils::generate_deterministic_keypair, AttestationData, BeaconBlockHeader, Hash256,
PublicKeyBytes,
};
pub const DEFAULT_VALIDATOR_INDEX: usize = 0;
pub const DEFAULT_DOMAIN: Hash256 = Hash256::zero();
pub const DEFAULT_GENESIS_VALIDATORS_ROOT: Hash256 = Hash256::zero();
pub fn pubkey(index: usize) -> PublicKey {
generate_deterministic_keypair(index).pk
pub fn pubkey(index: usize) -> PublicKeyBytes {
generate_deterministic_keypair(index).pk.compress()
}
pub struct Test<T> {
pubkey: PublicKey,
pubkey: PublicKeyBytes,
data: T,
domain: Hash256,
expected: Result<Safe, NotSafe>,
@@ -24,7 +25,7 @@ impl<T> Test<T> {
Self::with_pubkey(pubkey(DEFAULT_VALIDATOR_INDEX), data)
}
pub fn with_pubkey(pubkey: PublicKey, data: T) -> Self {
pub fn with_pubkey(pubkey: PublicKeyBytes, data: T) -> Self {
Self {
pubkey,
data,
@@ -58,7 +59,7 @@ impl<T> Test<T> {
pub struct StreamTest<T> {
/// Validators to register.
pub registered_validators: Vec<PublicKey>,
pub registered_validators: Vec<PublicKeyBytes>,
/// Vector of cases and the value expected when calling `check_and_insert_X`.
pub cases: Vec<Test<T>>,
}
@@ -89,7 +90,7 @@ impl StreamTest<AttestationData> {
let slashing_db = SlashingDatabase::create(&slashing_db_file).unwrap();
for pubkey in &self.registered_validators {
slashing_db.register_validator(pubkey).unwrap();
slashing_db.register_validator(*pubkey).unwrap();
}
for (i, test) in self.cases.iter().enumerate() {
@@ -112,7 +113,7 @@ impl StreamTest<BeaconBlockHeader> {
let slashing_db = SlashingDatabase::create(&slashing_db_file).unwrap();
for pubkey in &self.registered_validators {
slashing_db.register_validator(pubkey).unwrap();
slashing_db.register_validator(*pubkey).unwrap();
}
for (i, test) in self.cases.iter().enumerate() {

View File

@@ -11,7 +11,7 @@ use slot_clock::SlotClock;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
use tokio::time::{interval_at, sleep_until, Duration, Instant};
use tokio::time::{sleep, sleep_until, Duration, Instant};
use tree_hash::TreeHash;
use types::{
AggregateSignature, Attestation, AttestationData, BitList, ChainSpec, CommitteeIndex, EthSpec,
@@ -20,7 +20,7 @@ use types::{
/// Builds an `AttestationService`.
pub struct AttestationServiceBuilder<T, E: EthSpec> {
duties_service: Option<DutiesService<T, E>>,
duties_service: Option<Arc<DutiesService<T, E>>>,
validator_store: Option<ValidatorStore<T, E>>,
slot_clock: Option<T>,
beacon_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
@@ -38,7 +38,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationServiceBuilder<T, E> {
}
}
pub fn duties_service(mut self, service: DutiesService<T, E>) -> Self {
pub fn duties_service(mut self, service: Arc<DutiesService<T, E>>) -> Self {
self.duties_service = Some(service);
self
}
@@ -88,7 +88,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationServiceBuilder<T, E> {
/// Helper to minimise `Arc` usage.
pub struct Inner<T, E: EthSpec> {
duties_service: DutiesService<T, E>,
duties_service: Arc<DutiesService<T, E>>,
validator_store: ValidatorStore<T, E>,
slot_clock: T,
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
@@ -137,32 +137,31 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
"next_update_millis" => duration_to_next_slot.as_millis()
);
let mut interval = {
// Note: `interval_at` panics if `slot_duration` is 0
interval_at(
Instant::now() + duration_to_next_slot + slot_duration / 3,
slot_duration,
)
};
let executor = self.context.executor.clone();
let interval_fut = async move {
loop {
interval.tick().await;
let log = self.context.log();
if let Some(duration_to_next_slot) = self.slot_clock.duration_to_next_slot() {
sleep(duration_to_next_slot + slot_duration / 3).await;
let log = self.context.log();
if let Err(e) = self.spawn_attestation_tasks(slot_duration) {
crit!(
log,
"Failed to spawn attestation tasks";
"error" => e
)
if let Err(e) = self.spawn_attestation_tasks(slot_duration) {
crit!(
log,
"Failed to spawn attestation tasks";
"error" => e
)
} else {
trace!(
log,
"Spawned attestation tasks";
)
}
} else {
trace!(
log,
"Spawned attestation tasks";
)
error!(log, "Failed to read slot clock");
// If we can't read the slot clock, just wait another slot.
sleep(slot_duration).await;
continue;
}
}
};
@@ -192,12 +191,9 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
.attesters(slot)
.into_iter()
.fold(HashMap::new(), |mut map, duty_and_proof| {
if let Some(committee_index) = duty_and_proof.duty.attestation_committee_index {
let validator_duties = map.entry(committee_index).or_insert_with(Vec::new);
validator_duties.push(duty_and_proof);
}
map.entry(duty_and_proof.duty.committee_index)
.or_insert_with(Vec::new)
.push(duty_and_proof);
map
});
@@ -355,43 +351,27 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
let mut attestations = Vec::with_capacity(validator_duties.len());
for duty in validator_duties {
// Ensure that all required fields are present in the validator duty.
let (
duty_slot,
duty_committee_index,
validator_committee_position,
_,
_,
committee_length,
) = if let Some(tuple) = duty.attestation_duties() {
tuple
} else {
crit!(
log,
"Missing validator duties when signing";
"duties" => format!("{:?}", duty)
);
continue;
};
for duty_and_proof in validator_duties {
let duty = &duty_and_proof.duty;
// Ensure that the attestation matches the duties.
if duty_slot != attestation_data.slot || duty_committee_index != attestation_data.index
#[allow(clippy::suspicious_operation_groupings)]
if duty.slot != attestation_data.slot || duty.committee_index != attestation_data.index
{
crit!(
log,
"Inconsistent validator duties during signing";
"validator" => format!("{:?}", duty.validator_pubkey()),
"duty_slot" => duty_slot,
"validator" => ?duty.pubkey,
"duty_slot" => duty.slot,
"attestation_slot" => attestation_data.slot,
"duty_index" => duty_committee_index,
"duty_index" => duty.committee_index,
"attestation_index" => attestation_data.index,
);
continue;
}
let mut attestation = Attestation {
aggregation_bits: BitList::with_capacity(committee_length as usize).unwrap(),
aggregation_bits: BitList::with_capacity(duty.committee_length as usize).unwrap(),
data: attestation_data.clone(),
signature: AggregateSignature::infinity(),
};
@@ -399,8 +379,8 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
if self
.validator_store
.sign_attestation(
duty.validator_pubkey(),
validator_committee_position,
&duty.pubkey,
duty.validator_committee_index as usize,
&mut attestation,
current_epoch,
)
@@ -490,6 +470,8 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
let mut signed_aggregate_and_proofs = Vec::new();
for duty_and_proof in validator_duties {
let duty = &duty_and_proof.duty;
let selection_proof = if let Some(proof) = duty_and_proof.selection_proof.as_ref() {
proof
} else {
@@ -497,26 +479,18 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
// subscribed aggregators.
continue;
};
let (duty_slot, duty_committee_index, _, validator_index, _, _) =
if let Some(tuple) = duty_and_proof.attestation_duties() {
tuple
} else {
crit!(log, "Missing duties when signing aggregate");
continue;
};
let pubkey = &duty_and_proof.duty.validator_pubkey;
let slot = attestation_data.slot;
let committee_index = attestation_data.index;
if duty_slot != slot || duty_committee_index != committee_index {
if duty.slot != slot || duty.committee_index != committee_index {
crit!(log, "Inconsistent validator duties during signing");
continue;
}
if let Some(aggregate) = self.validator_store.produce_signed_aggregate_and_proof(
pubkey,
validator_index,
&duty.pubkey,
duty.validator_index,
aggregated_attestation.clone(),
selection_proof.clone(),
) {

View File

@@ -5,13 +5,13 @@ use crate::{
use crate::{http_metrics::metrics, validator_store::ValidatorStore};
use environment::RuntimeContext;
use eth2::types::Graffiti;
use futures::channel::mpsc::Receiver;
use futures::{StreamExt, TryFutureExt};
use futures::TryFutureExt;
use slog::{crit, debug, error, info, trace, warn};
use slot_clock::SlotClock;
use std::ops::Deref;
use std::sync::Arc;
use types::{EthSpec, PublicKey, Slot};
use tokio::sync::mpsc;
use types::{EthSpec, PublicKeyBytes, Slot};
/// Builds a `BlockService`.
pub struct BlockServiceBuilder<T, E: EthSpec> {
@@ -121,13 +121,13 @@ impl<T, E: EthSpec> Deref for BlockService<T, E> {
/// Notification from the duties service that we should try to produce a block.
pub struct BlockServiceNotification {
pub slot: Slot,
pub block_proposers: Vec<PublicKey>,
pub block_proposers: Vec<PublicKeyBytes>,
}
impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
pub fn start_update_service(
self,
notification_rx: Receiver<BlockServiceNotification>,
mut notification_rx: mpsc::Receiver<BlockServiceNotification>,
) -> Result<(), String> {
let log = self.context.log().clone();
@@ -135,14 +135,16 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
let executor = self.inner.context.executor.clone();
let block_service_fut = notification_rx.for_each(move |notif| {
let service = self.clone();
executor.spawn(
async move {
service.do_update(notif).await.ok();
}
});
executor.spawn(block_service_fut, "block_service");
while let Some(notif) = notification_rx.recv().await {
let service = self.clone();
service.do_update(notif).await.ok();
}
debug!(log, "Block service shutting down");
},
"block_service",
);
Ok(())
}
@@ -222,7 +224,11 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
}
/// Produce a block at the given slot for validator_pubkey
async fn publish_block(self, slot: Slot, validator_pubkey: PublicKey) -> Result<(), String> {
async fn publish_block(
self,
slot: Slot,
validator_pubkey: PublicKeyBytes,
) -> Result<(), String> {
let log = self.context.log();
let _timer =
metrics::start_timer_vec(&metrics::BLOCK_SERVICE_TIMES, &[metrics::BEACON_BLOCK]);

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,12 @@ use environment::RuntimeContext;
use eth2::types::StateId;
use futures::future::FutureExt;
use parking_lot::RwLock;
use slog::Logger;
use slog::{debug, trace};
use slog::{error, Logger};
use slot_clock::SlotClock;
use std::ops::Deref;
use std::sync::Arc;
use tokio::time::{interval_at, Duration, Instant};
use tokio::time::{sleep, Duration};
use types::{EthSpec, Fork};
/// Delay this period of time after the slot starts. This allows the node to process the new slot.
@@ -139,33 +139,31 @@ impl<T: SlotClock + 'static, E: EthSpec> ForkService<T, E> {
/// Starts the service that periodically polls for the `Fork`.
pub fn start_update_service(self, context: &RuntimeContext<E>) -> Result<(), String> {
let spec = &context.eth2_config.spec;
let duration_to_next_epoch = self
.slot_clock
.duration_to_next_epoch(E::slots_per_epoch())
.ok_or("Unable to determine duration to next epoch")?;
let mut interval = {
let slot_duration = Duration::from_secs(spec.seconds_per_slot);
// Note: interval_at panics if `slot_duration * E::slots_per_epoch()` = 0
interval_at(
Instant::now() + duration_to_next_epoch + TIME_DELAY_FROM_SLOT,
slot_duration * E::slots_per_epoch() as u32,
)
};
// Run an immediate update before starting the updater service.
context
.executor
.spawn(self.clone().do_update().map(|_| ()), "fork service update");
let executor = context.executor.clone();
let log = context.log().clone();
let spec = E::default_spec();
let interval_fut = async move {
loop {
interval.tick().await;
// Run this poll before the wait, this should hopefully download the fork before the
// other services need them.
self.clone().do_update().await.ok();
if let Some(duration_to_next_epoch) =
self.slot_clock.duration_to_next_epoch(E::slots_per_epoch())
{
sleep(duration_to_next_epoch + TIME_DELAY_FROM_SLOT).await;
} else {
error!(log, "Failed to read slot clock");
// If we can't read the slot clock, just wait another slot.
sleep(Duration::from_secs(spec.seconds_per_slot)).await;
continue;
}
}
};

View File

@@ -5,7 +5,7 @@ use std::io::{prelude::*, BufReader};
use std::path::PathBuf;
use std::str::FromStr;
use bls::blst_implementations::PublicKey;
use bls::blst_implementations::PublicKeyBytes;
use types::{graffiti::GraffitiString, Graffiti};
#[derive(Debug)]
@@ -26,7 +26,7 @@ pub enum Error {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraffitiFile {
graffiti_path: PathBuf,
graffitis: HashMap<PublicKey, Graffiti>,
graffitis: HashMap<PublicKeyBytes, Graffiti>,
default: Option<Graffiti>,
}
@@ -44,7 +44,10 @@ impl GraffitiFile {
/// default graffiti.
///
/// Returns an error if loading from the graffiti file fails.
pub fn load_graffiti(&mut self, public_key: &PublicKey) -> Result<Option<Graffiti>, Error> {
pub fn load_graffiti(
&mut self,
public_key: &PublicKeyBytes,
) -> Result<Option<Graffiti>, Error> {
self.read_graffiti_file()?;
Ok(self.graffitis.get(public_key).copied().or(self.default))
}
@@ -78,7 +81,7 @@ impl GraffitiFile {
/// `Ok((None, graffiti))` represents the graffiti for the default key.
/// `Ok((Some(pk), graffiti))` represents graffiti for the public key `pk`.
/// Returns an error if the line is in the wrong format or does not contain a valid public key or graffiti.
fn read_line(line: &str) -> Result<(Option<PublicKey>, Graffiti), Error> {
fn read_line(line: &str) -> Result<(Option<PublicKeyBytes>, Graffiti), Error> {
if let Some(i) = line.find(':') {
let (key, value) = line.split_at(i);
// Note: `value.len() >=1` so `value[1..]` is safe
@@ -88,7 +91,7 @@ fn read_line(line: &str) -> Result<(Option<PublicKey>, Graffiti), Error> {
if key == "default" {
Ok((None, graffiti))
} else {
let pk = PublicKey::from_str(&key).map_err(Error::InvalidPublicKey)?;
let pk = PublicKeyBytes::from_str(&key).map_err(Error::InvalidPublicKey)?;
Ok((Some(pk), graffiti))
}
} else {
@@ -114,9 +117,9 @@ mod tests {
// Create a graffiti file in the required format and return a path to the file.
fn create_graffiti_file() -> PathBuf {
let temp = TempDir::new().unwrap();
let pk1 = PublicKey::deserialize(&hex::decode(&PK1[2..]).unwrap()).unwrap();
let pk2 = PublicKey::deserialize(&hex::decode(&PK2[2..]).unwrap()).unwrap();
let pk3 = PublicKey::deserialize(&hex::decode(&PK3[2..]).unwrap()).unwrap();
let pk1 = PublicKeyBytes::deserialize(&hex::decode(&PK1[2..]).unwrap()).unwrap();
let pk2 = PublicKeyBytes::deserialize(&hex::decode(&PK2[2..]).unwrap()).unwrap();
let pk3 = PublicKeyBytes::deserialize(&hex::decode(&PK3[2..]).unwrap()).unwrap();
let file_name = temp.into_path().join("graffiti.txt");
@@ -143,9 +146,9 @@ mod tests {
let graffiti_file_path = create_graffiti_file();
let mut gf = GraffitiFile::new(graffiti_file_path);
let pk1 = PublicKey::deserialize(&hex::decode(&PK1[2..]).unwrap()).unwrap();
let pk2 = PublicKey::deserialize(&hex::decode(&PK2[2..]).unwrap()).unwrap();
let pk3 = PublicKey::deserialize(&hex::decode(&PK3[2..]).unwrap()).unwrap();
let pk1 = PublicKeyBytes::deserialize(&hex::decode(&PK1[2..]).unwrap()).unwrap();
let pk2 = PublicKeyBytes::deserialize(&hex::decode(&PK2[2..]).unwrap()).unwrap();
let pk3 = PublicKeyBytes::deserialize(&hex::decode(&PK3[2..]).unwrap()).unwrap();
// Read once
gf.read_graffiti_file().unwrap();
@@ -165,7 +168,7 @@ mod tests {
);
// Random pk should return the default graffiti
let random_pk = Keypair::random().pk;
let random_pk = Keypair::random().pk.compress();
assert_eq!(
gf.load_graffiti(&random_pk).unwrap().unwrap(),
GraffitiString::from_str(DEFAULT_GRAFFITI).unwrap().into()

View File

@@ -13,6 +13,13 @@ pub const ATTESTATIONS: &str = "attestations";
pub const AGGREGATES: &str = "aggregates";
pub const CURRENT_EPOCH: &str = "current_epoch";
pub const NEXT_EPOCH: &str = "next_epoch";
pub const UPDATE_INDICES: &str = "update_indices";
pub const UPDATE_ATTESTERS_CURRENT_EPOCH: &str = "update_attesters_current_epoch";
pub const UPDATE_ATTESTERS_NEXT_EPOCH: &str = "update_attesters_next_epoch";
pub const UPDATE_ATTESTERS_FETCH: &str = "update_attesters_fetch";
pub const UPDATE_ATTESTERS_STORE: &str = "update_attesters_store";
pub const UPDATE_PROPOSERS: &str = "update_proposers";
pub const SUBSCRIPTIONS: &str = "subscriptions";
pub use lighthouse_metrics::*;
@@ -84,6 +91,10 @@ lazy_static::lazy_static! {
"Number of attesters on this host",
&["task"]
);
pub static ref PROPOSAL_CHANGED: Result<IntCounter> = try_create_int_counter(
"vc_beacon_block_proposal_changed",
"A duties update discovered a new block proposer for the current slot",
);
/*
* Endpoint metrics
*/

View File

@@ -36,7 +36,7 @@ impl From<String> for Error {
/// Contains objects which have shared access from inside/outside of the metrics server.
pub struct Shared<T: EthSpec> {
pub validator_store: Option<ValidatorStore<SystemTimeSlotClock, T>>,
pub duties_service: Option<DutiesService<SystemTimeSlotClock, T>>,
pub duties_service: Option<Arc<DutiesService<SystemTimeSlotClock, T>>>,
pub genesis_time: Option<u64>,
}

View File

@@ -20,7 +20,7 @@ use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io;
use std::path::PathBuf;
use types::{Graffiti, Keypair, PublicKey};
use types::{Graffiti, Keypair, PublicKey, PublicKeyBytes};
use crate::key_cache;
use crate::key_cache::KeyCache;
@@ -284,7 +284,7 @@ pub struct InitializedValidators {
/// The directory that the `self.definitions` will be saved into.
validators_dir: PathBuf,
/// The canonical set of validators.
validators: HashMap<PublicKey, InitializedValidator>,
validators: HashMap<PublicKeyBytes, InitializedValidator>,
/// For logging via `slog`.
log: Logger,
}
@@ -317,13 +317,13 @@ impl InitializedValidators {
}
/// Iterate through all **enabled** voting public keys in `self`.
pub fn iter_voting_pubkeys(&self) -> impl Iterator<Item = &PublicKey> {
pub fn iter_voting_pubkeys(&self) -> impl Iterator<Item = &PublicKeyBytes> {
self.validators.iter().map(|(pubkey, _)| pubkey)
}
/// Returns the voting `Keypair` for a given voting `PublicKey`, if that validator is known to
/// `self` **and** the validator is enabled.
pub fn voting_keypair(&self, voting_public_key: &PublicKey) -> Option<&Keypair> {
pub fn voting_keypair(&self, voting_public_key: &PublicKeyBytes) -> Option<&Keypair> {
self.validators
.get(voting_public_key)
.map(|v| v.voting_keypair())
@@ -366,7 +366,7 @@ impl InitializedValidators {
}
/// Returns the `graffiti` for a given public key specified in the `ValidatorDefinitions`.
pub fn graffiti(&self, public_key: &PublicKey) -> Option<Graffiti> {
pub fn graffiti(&self, public_key: &PublicKeyBytes) -> Option<Graffiti> {
self.validators.get(public_key).and_then(|v| v.graffiti)
}
@@ -513,7 +513,9 @@ impl InitializedValidators {
voting_keystore_path,
..
} => {
if self.validators.contains_key(&def.voting_public_key) {
let pubkey_bytes = def.voting_public_key.compress();
if self.validators.contains_key(&pubkey_bytes) {
continue;
}
@@ -536,7 +538,7 @@ impl InitializedValidators {
.map(|l| l.path().to_owned());
self.validators
.insert(init.voting_public_key().clone(), init);
.insert(init.voting_public_key().compress(), init);
info!(
self.log,
"Enabled validator";
@@ -569,7 +571,7 @@ impl InitializedValidators {
}
}
} else {
self.validators.remove(&def.voting_public_key);
self.validators.remove(&def.voting_public_key.compress());
match &def.signing_definition {
SigningDefinition::LocalKeystore {
voting_keystore_path,

View File

@@ -11,7 +11,6 @@ mod http_metrics;
mod initialized_validators;
mod key_cache;
mod notifier;
mod validator_duty;
mod validator_store;
pub mod http_api;
@@ -26,12 +25,11 @@ use account_utils::validator_definitions::ValidatorDefinitions;
use attestation_service::{AttestationService, AttestationServiceBuilder};
use block_service::{BlockService, BlockServiceBuilder};
use clap::ArgMatches;
use duties_service::{DutiesService, DutiesServiceBuilder};
use duties_service::DutiesService;
use environment::RuntimeContext;
use eth2::types::StateId;
use eth2::{reqwest::ClientBuilder, BeaconNodeHttpClient, StatusCode, Url};
use fork_service::{ForkService, ForkServiceBuilder};
use futures::channel::mpsc;
use http_api::ApiSecret;
use initialized_validators::InitializedValidators;
use notifier::spawn_notifier;
@@ -44,7 +42,10 @@ use std::marker::PhantomData;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::time::{sleep, Duration};
use tokio::{
sync::mpsc,
time::{sleep, Duration},
};
use types::{EthSpec, Fork, Hash256};
use validator_store::ValidatorStore;
@@ -60,7 +61,7 @@ const HTTP_TIMEOUT: Duration = Duration::from_secs(12);
#[derive(Clone)]
pub struct ProductionValidatorClient<T: EthSpec> {
context: RuntimeContext<T>,
duties_service: DutiesService<SystemTimeSlotClock, T>,
duties_service: Arc<DutiesService<SystemTimeSlotClock, T>>,
fork_service: ForkService<SystemTimeSlotClock, T>,
block_service: BlockService<SystemTimeSlotClock, T>,
attestation_service: AttestationService<SystemTimeSlotClock, T>,
@@ -285,13 +286,22 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
validator_store.prune_slashing_protection_db(slot.epoch(T::slots_per_epoch()), true);
}
let duties_service = DutiesServiceBuilder::new()
.slot_clock(slot_clock.clone())
.validator_store(validator_store.clone())
.beacon_nodes(beacon_nodes.clone())
.allow_unsynced_beacon_node(config.allow_unsynced_beacon_node)
.runtime_context(context.service_context("duties".into()))
.build()?;
let duties_context = context.service_context("duties".into());
let duties_service = Arc::new(DutiesService {
attesters: <_>::default(),
proposers: <_>::default(),
indices: <_>::default(),
slot_clock: slot_clock.clone(),
beacon_nodes: beacon_nodes.clone(),
validator_store: validator_store.clone(),
require_synced: if config.allow_unsynced_beacon_node {
RequireSynced::Yes
} else {
RequireSynced::No
},
spec: context.eth2_config.spec.clone(),
context: duties_context,
});
// Update the metrics server.
if let Some(ctx) = &http_metrics_ctx {
@@ -343,13 +353,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
let (block_service_tx, block_service_rx) = mpsc::channel(channel_capacity);
let log = self.context.log();
self.duties_service
.clone()
.start_update_service(
block_service_tx,
Arc::new(self.context.eth2_config.spec.clone()),
)
.map_err(|e| format!("Unable to start duties service: {}", e))?;
duties_service::start_update_service(self.duties_service.clone(), block_service_tx);
self.fork_service
.clone()

View File

@@ -1,7 +1,7 @@
use crate::ProductionValidatorClient;
use slog::{error, info};
use crate::{DutiesService, ProductionValidatorClient};
use slog::{error, info, Logger};
use slot_clock::SlotClock;
use tokio::time::{interval_at, Duration, Instant};
use tokio::time::{sleep, Duration};
use types::EthSpec;
/// Spawns a notifier service which periodically logs information about the node.
@@ -11,86 +11,19 @@ pub fn spawn_notifier<T: EthSpec>(client: &ProductionValidatorClient<T>) -> Resu
let duties_service = client.duties_service.clone();
let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot);
let duration_to_next_slot = duties_service
.slot_clock
.duration_to_next_slot()
.ok_or("slot_notifier unable to determine time to next slot")?;
// Run the notifier half way through each slot.
let start_instant = Instant::now() + duration_to_next_slot + (slot_duration / 2);
let mut interval = interval_at(start_instant, slot_duration);
let interval_fut = async move {
let log = context.log();
loop {
interval.tick().await;
let num_available = duties_service.beacon_nodes.num_available().await;
let num_synced = duties_service.beacon_nodes.num_synced().await;
let num_total = duties_service.beacon_nodes.num_total().await;
if num_synced > 0 {
info!(
log,
"Connected to beacon node(s)";
"total" => num_total,
"available" => num_available,
"synced" => num_synced,
)
if let Some(duration_to_next_slot) = duties_service.slot_clock.duration_to_next_slot() {
sleep(duration_to_next_slot + slot_duration / 2).await;
notify(&duties_service, &log).await;
} else {
error!(
log,
"No synced beacon nodes";
"total" => num_total,
"available" => num_available,
"synced" => num_synced,
)
}
if let Some(slot) = duties_service.slot_clock.now() {
let epoch = slot.epoch(T::slots_per_epoch());
let total_validators = duties_service.total_validator_count();
let proposing_validators = duties_service.proposer_count(epoch);
let attesting_validators = duties_service.attester_count(epoch);
if total_validators == 0 {
info!(
log,
"No validators present";
"msg" => "see `lighthouse account validator create --help` \
or the HTTP API documentation"
)
} else if total_validators == attesting_validators {
info!(
log,
"All validators active";
"proposers" => proposing_validators,
"active_validators" => attesting_validators,
"total_validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
} else if attesting_validators > 0 {
info!(
log,
"Some validators active";
"proposers" => proposing_validators,
"active_validators" => attesting_validators,
"total_validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
} else {
info!(
log,
"Awaiting activation";
"validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
}
} else {
error!(log, "Unable to read slot clock");
error!(log, "Failed to read slot clock");
// If we can't read the slot clock, just wait another slot.
sleep(slot_duration).await;
continue;
}
}
};
@@ -98,3 +31,77 @@ pub fn spawn_notifier<T: EthSpec>(client: &ProductionValidatorClient<T>) -> Resu
executor.spawn(interval_fut, "validator_notifier");
Ok(())
}
/// Performs a single notification routine.
async fn notify<T: SlotClock + 'static, E: EthSpec>(
duties_service: &DutiesService<T, E>,
log: &Logger,
) {
let num_available = duties_service.beacon_nodes.num_available().await;
let num_synced = duties_service.beacon_nodes.num_synced().await;
let num_total = duties_service.beacon_nodes.num_total().await;
if num_synced > 0 {
info!(
log,
"Connected to beacon node(s)";
"total" => num_total,
"available" => num_available,
"synced" => num_synced,
)
} else {
error!(
log,
"No synced beacon nodes";
"total" => num_total,
"available" => num_available,
"synced" => num_synced,
)
}
if let Some(slot) = duties_service.slot_clock.now() {
let epoch = slot.epoch(E::slots_per_epoch());
let total_validators = duties_service.total_validator_count();
let proposing_validators = duties_service.proposer_count(epoch);
let attesting_validators = duties_service.attester_count(epoch);
if total_validators == 0 {
info!(
log,
"No validators present";
"msg" => "see `lighthouse account validator create --help` \
or the HTTP API documentation"
)
} else if total_validators == attesting_validators {
info!(
log,
"All validators active";
"proposers" => proposing_validators,
"active_validators" => attesting_validators,
"total_validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
} else if attesting_validators > 0 {
info!(
log,
"Some validators active";
"proposers" => proposing_validators,
"active_validators" => attesting_validators,
"total_validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
} else {
info!(
log,
"Awaiting activation";
"validators" => total_validators,
"epoch" => format!("{}", epoch),
"slot" => format!("{}", slot),
);
}
} else {
error!(log, "Unable to read slot clock");
}
}

View File

@@ -1,188 +0,0 @@
use eth2::{
types::{BeaconCommitteeSubscription, StateId, ValidatorId},
BeaconNodeHttpClient,
};
use serde::{Deserialize, Serialize};
use slog::{error, Logger};
use std::collections::HashMap;
use types::{CommitteeIndex, Epoch, PublicKey, PublicKeyBytes, Slot};
/// This struct is being used as a shim since we deprecated the `rest_api` in favour of `http_api`.
///
/// Tracking issue: https://github.com/sigp/lighthouse/issues/1643
// NOTE: if you add or remove fields, please adjust `eq_ignoring_proposal_slots`
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct ValidatorDuty {
/// The validator's BLS public key, uniquely identifying them.
pub validator_pubkey: PublicKey,
/// The validator's index in `state.validators`
pub validator_index: Option<u64>,
/// The slot at which the validator must attest.
pub attestation_slot: Option<Slot>,
/// The index of the committee within `slot` of which the validator is a member.
pub attestation_committee_index: Option<CommitteeIndex>,
/// The position of the validator in the committee.
pub attestation_committee_position: Option<usize>,
/// The committee count at `attestation_slot`.
pub committee_count_at_slot: Option<u64>,
/// The number of validators in the committee.
pub committee_length: Option<u64>,
/// The slots in which a validator must propose a block (can be empty).
///
/// Should be set to `None` when duties are not yet known (before the current epoch).
pub block_proposal_slots: Option<Vec<Slot>>,
}
impl ValidatorDuty {
/// Instantiate `Self` as if there are no known dutes for `validator_pubkey`.
fn no_duties(validator_pubkey: PublicKey, validator_index: Option<u64>) -> Self {
ValidatorDuty {
validator_pubkey,
validator_index,
attestation_slot: None,
attestation_committee_index: None,
attestation_committee_position: None,
committee_count_at_slot: None,
committee_length: None,
block_proposal_slots: None,
}
}
/// Instantiate `Self` by performing requests on the `beacon_node`.
///
/// Will only request proposer duties if `current_epoch == request_epoch`.
pub async fn download(
beacon_node: &BeaconNodeHttpClient,
current_epoch: Epoch,
request_epoch: Epoch,
mut pubkeys: Vec<(PublicKey, Option<u64>)>,
log: &Logger,
) -> Result<Vec<ValidatorDuty>, String> {
for (pubkey, index_opt) in &mut pubkeys {
if index_opt.is_none() {
*index_opt = beacon_node
.get_beacon_states_validator_id(
StateId::Head,
&ValidatorId::PublicKey(PublicKeyBytes::from(&*pubkey)),
)
.await
.map_err(|e| {
error!(
log,
"Failed to obtain validator index";
"pubkey" => ?pubkey,
"error" => ?e
)
})
// Supress the error since we've already logged an error and we don't want to
// stop the rest of the code.
.ok()
.and_then(|body_opt| body_opt.map(|body| body.data.index));
}
}
// Query for all block proposer duties in the current epoch and map the response by index.
let proposal_slots_by_index: HashMap<u64, Vec<Slot>> = if current_epoch == request_epoch {
beacon_node
.get_validator_duties_proposer(current_epoch)
.await
.map(|resp| resp.data)
// Exit early if there's an error.
.map_err(|e| format!("Failed to get proposer indices: {:?}", e))?
.into_iter()
.fold(
HashMap::with_capacity(pubkeys.len()),
|mut map, proposer_data| {
map.entry(proposer_data.validator_index)
.or_insert_with(Vec::new)
.push(proposer_data.slot);
map
},
)
} else {
HashMap::new()
};
let query_indices = pubkeys
.iter()
.filter_map(|(_, index_opt)| *index_opt)
.collect::<Vec<_>>();
let attester_data_map = beacon_node
.post_validator_duties_attester(request_epoch, query_indices.as_slice())
.await
.map(|resp| resp.data)
// Exit early if there's an error.
.map_err(|e| format!("Failed to get attester duties: {:?}", e))?
.into_iter()
.fold(
HashMap::with_capacity(pubkeys.len()),
|mut map, attester_data| {
map.insert(attester_data.validator_index, attester_data);
map
},
);
let duties = pubkeys
.into_iter()
.map(|(pubkey, index_opt)| {
if let Some(index) = index_opt {
if let Some(attester_data) = attester_data_map.get(&index) {
match attester_data.pubkey.decompress() {
Ok(pubkey) => ValidatorDuty {
validator_pubkey: pubkey,
validator_index: Some(attester_data.validator_index),
attestation_slot: Some(attester_data.slot),
attestation_committee_index: Some(attester_data.committee_index),
attestation_committee_position: Some(
attester_data.validator_committee_index as usize,
),
committee_count_at_slot: Some(attester_data.committees_at_slot),
committee_length: Some(attester_data.committee_length),
block_proposal_slots: proposal_slots_by_index
.get(&attester_data.validator_index)
.cloned(),
},
Err(e) => {
error!(
log,
"Could not deserialize validator public key";
"error" => format!("{:?}", e),
"validator_index" => attester_data.validator_index
);
Self::no_duties(pubkey, Some(index))
}
}
} else {
Self::no_duties(pubkey, Some(index))
}
} else {
Self::no_duties(pubkey, None)
}
})
.collect();
Ok(duties)
}
/// Return `true` if these validator duties are equal, ignoring their `block_proposal_slots`.
pub fn eq_ignoring_proposal_slots(&self, other: &Self) -> bool {
self.validator_pubkey == other.validator_pubkey
&& self.validator_index == other.validator_index
&& self.attestation_slot == other.attestation_slot
&& self.attestation_committee_index == other.attestation_committee_index
&& self.attestation_committee_position == other.attestation_committee_position
&& self.committee_count_at_slot == other.committee_count_at_slot
&& self.committee_length == other.committee_length
}
/// Generate a subscription for `self`, if `self` has appropriate attestation duties.
pub fn subscription(&self, is_aggregator: bool) -> Option<BeaconCommitteeSubscription> {
Some(BeaconCommitteeSubscription {
validator_index: self.validator_index?,
committee_index: self.attestation_committee_index?,
committees_at_slot: self.committee_count_at_slot?,
slot: self.attestation_slot?,
is_aggregator,
})
}
}

View File

@@ -11,7 +11,7 @@ use std::sync::Arc;
use tempfile::TempDir;
use types::{
graffiti::GraffitiString, Attestation, BeaconBlock, ChainSpec, Domain, Epoch, EthSpec, Fork,
Graffiti, Hash256, Keypair, PublicKey, SelectionProof, Signature, SignedAggregateAndProof,
Graffiti, Hash256, Keypair, PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof,
SignedBeaconBlock, SignedRoot, Slot,
};
use validator_dir::ValidatorDir;
@@ -106,7 +106,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
.map_err(|e| format!("failed to create validator definitions: {:?}", e))?;
self.slashing_protection
.register_validator(&validator_def.voting_public_key)
.register_validator(validator_def.voting_public_key.compress())
.map_err(|e| format!("failed to register validator: {:?}", e))?;
validator_def.enabled = enable;
@@ -120,7 +120,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
Ok(validator_def)
}
pub fn voting_pubkeys(&self) -> Vec<PublicKey> {
pub fn voting_pubkeys(&self) -> Vec<PublicKeyBytes> {
self.validators
.read()
.iter_voting_pubkeys()
@@ -136,7 +136,11 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
self.fork_service.fork()
}
pub fn randao_reveal(&self, validator_pubkey: &PublicKey, epoch: Epoch) -> Option<Signature> {
pub fn randao_reveal(
&self,
validator_pubkey: &PublicKeyBytes,
epoch: Epoch,
) -> Option<Signature> {
self.validators
.read()
.voting_keypair(validator_pubkey)
@@ -153,13 +157,13 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
})
}
pub fn graffiti(&self, validator_pubkey: &PublicKey) -> Option<Graffiti> {
pub fn graffiti(&self, validator_pubkey: &PublicKeyBytes) -> Option<Graffiti> {
self.validators.read().graffiti(validator_pubkey)
}
pub fn sign_block(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
block: BeaconBlock<E>,
current_slot: Slot,
) -> Option<SignedBeaconBlock<E>> {
@@ -236,7 +240,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
pub fn sign_attestation(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
validator_committee_position: usize,
attestation: &mut Attestation<E>,
current_epoch: Epoch,
@@ -334,7 +338,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
/// modified by actors other than the signing validator.
pub fn produce_signed_aggregate_and_proof(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
validator_index: u64,
aggregate: Attestation<E>,
selection_proof: SelectionProof,
@@ -359,7 +363,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
/// `validator_pubkey`.
pub fn produce_selection_proof(
&self,
validator_pubkey: &PublicKey,
validator_pubkey: &PublicKeyBytes,
slot: Slot,
) -> Option<SelectionProof> {
let validators = self.validators.read();