Files
lighthouse/validator_client/validator_store/src/lib.rs
Shane K Moore 4b3a9d3d10 Refactor/stream vc vote publishing (#8880)
Changes four `ValidatorStore` batch signing methods to return `impl Stream` instead of `Future`. Services consume the stream and publish each batch as it arrives.  No behavioral change for lh since `LighthouseValidatorStore` wraps everything in `stream::once`

Also replaces anonymous tuples in method signatures with named structs


Co-Authored-By: shane-moore <skm1790@gmail.com>

Co-Authored-By: Michael Sproul <michaelsproul@users.noreply.github.com>

Co-Authored-By: Mac L <mjladson@pm.me>
2026-03-12 09:53:32 +00:00

287 lines
11 KiB
Rust

use bls::{PublicKeyBytes, Signature};
use eth2::types::{FullBlockContents, PublishBlockRequest};
use futures::Stream;
use slashing_protection::NotSafe;
use std::fmt::Debug;
use std::future::Future;
use std::sync::Arc;
use types::{
Address, Attestation, AttestationError, BlindedBeaconBlock, Epoch, EthSpec,
ExecutionPayloadEnvelope, Graffiti, Hash256, SelectionProof, SignedAggregateAndProof,
SignedBlindedBeaconBlock, SignedContributionAndProof, SignedExecutionPayloadEnvelope,
SignedValidatorRegistrationData, Slot, SyncCommitteeContribution, SyncCommitteeMessage,
SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
};
#[derive(Debug, PartialEq, Clone)]
pub enum Error<T> {
DoppelgangerProtected(PublicKeyBytes),
UnknownToDoppelgangerService(PublicKeyBytes),
UnknownPubkey(PublicKeyBytes),
Slashable(NotSafe),
SameData,
GreaterThanCurrentSlot { slot: Slot, current_slot: Slot },
UnableToSignAttestation(AttestationError),
SpecificError(T),
ExecutorError,
Middleware(String),
}
impl<T> From<T> for Error<T> {
fn from(e: T) -> Self {
Error::SpecificError(e)
}
}
/// Input for batch attestation signing
pub struct AttestationToSign<E: EthSpec> {
pub validator_index: u64,
pub pubkey: PublicKeyBytes,
pub validator_committee_index: usize,
pub attestation: Attestation<E>,
}
/// Input for batch aggregate signing
pub struct AggregateToSign<E: EthSpec> {
pub pubkey: PublicKeyBytes,
pub aggregator_index: u64,
pub aggregate: Attestation<E>,
pub selection_proof: SelectionProof,
}
/// Input for batch sync committee message signing
pub struct SyncMessageToSign {
pub slot: Slot,
pub beacon_block_root: Hash256,
pub validator_index: u64,
pub pubkey: PublicKeyBytes,
}
/// Input for batch sync committee contribution signing
pub struct ContributionToSign<E: EthSpec> {
pub aggregator_index: u64,
pub aggregator_pubkey: PublicKeyBytes,
pub contribution: SyncCommitteeContribution<E>,
pub selection_proof: SyncSelectionProof,
}
/// A helper struct, used for passing data from the validator store to services.
pub struct ProposalData {
pub validator_index: Option<u64>,
pub fee_recipient: Option<Address>,
pub gas_limit: u64,
pub builder_proposals: bool,
}
pub trait ValidatorStore: Send + Sync {
type Error: Debug + Send + Sync;
type E: EthSpec;
/// Attempts to resolve the pubkey to a validator index.
///
/// It may return `None` if the `pubkey` is:
///
/// - Unknown.
/// - Known, but with an unknown index.
fn validator_index(&self, pubkey: &PublicKeyBytes) -> Option<u64>;
/// Returns all voting pubkeys for all enabled validators.
///
/// The `filter_func` allows for filtering pubkeys based upon their `DoppelgangerStatus`. There
/// are two primary functions used here:
///
/// - `DoppelgangerStatus::only_safe`: only returns pubkeys which have passed doppelganger
/// protection and are safe-enough to sign messages.
/// - `DoppelgangerStatus::ignored`: returns all the pubkeys from `only_safe` *plus* those still
/// undergoing protection. This is useful for collecting duties or other non-signing tasks.
fn voting_pubkeys<I, F>(&self, filter_func: F) -> I
where
I: FromIterator<PublicKeyBytes>,
F: Fn(DoppelgangerStatus) -> Option<PublicKeyBytes>;
/// Check if the `validator_pubkey` is permitted by the doppleganger protection to sign
/// messages.
fn doppelganger_protection_allows_signing(&self, validator_pubkey: PublicKeyBytes) -> bool;
fn num_voting_validators(&self) -> usize;
fn graffiti(&self, validator_pubkey: &PublicKeyBytes) -> Option<Graffiti>;
/// Returns the fee recipient for the given public key. The priority order for fetching
/// the fee recipient is:
/// 1. validator_definitions.yml
/// 2. process level fee recipient
fn get_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option<Address>;
/// Translate the `builder_proposals`, `builder_boost_factor` and
/// `prefer_builder_proposals` to a boost factor, if available.
/// - If `prefer_builder_proposals` is true, set boost factor to `u64::MAX` to indicate a
/// preference for builder payloads.
/// - If `builder_boost_factor` is a value other than None, return its value as the boost factor.
/// - If `builder_proposals` is set to false, set boost factor to 0 to indicate a preference for
/// local payloads.
/// - Else return `None` to indicate no preference between builder and local payloads.
fn determine_builder_boost_factor(&self, validator_pubkey: &PublicKeyBytes) -> Option<u64>;
fn randao_reveal(
&self,
validator_pubkey: PublicKeyBytes,
signing_epoch: Epoch,
) -> impl Future<Output = Result<Signature, Error<Self::Error>>> + Send;
fn set_validator_index(&self, validator_pubkey: &PublicKeyBytes, index: u64);
fn sign_block(
&self,
validator_pubkey: PublicKeyBytes,
block: UnsignedBlock<Self::E>,
current_slot: Slot,
) -> impl Future<Output = Result<SignedBlock<Self::E>, Error<Self::Error>>> + Send;
/// Sign a batch of `attestations` and apply slashing protection to them.
///
/// Returns a stream of batches of successfully signed attestations. Each batch contains
/// attestations that passed slashing protection, along with the validator index of the signer.
/// Eventually this will be replaced by `SingleAttestation` use.
///
/// Output:
///
/// * Vec of (validator_index, signed_attestation).
#[allow(clippy::type_complexity)]
fn sign_attestations(
self: &Arc<Self>,
attestations: Vec<AttestationToSign<Self::E>>,
) -> impl Stream<Item = Result<Vec<(u64, Attestation<Self::E>)>, Error<Self::Error>>> + Send;
fn sign_validator_registration_data(
&self,
validator_registration_data: ValidatorRegistrationData,
) -> impl Future<Output = Result<SignedValidatorRegistrationData, Error<Self::Error>>> + Send;
/// Produces a `SelectionProof` for the `slot`, signed by with corresponding secret key to
/// `validator_pubkey`.
fn produce_selection_proof(
&self,
validator_pubkey: PublicKeyBytes,
slot: Slot,
) -> impl Future<Output = Result<SelectionProof, Error<Self::Error>>> + Send;
/// Produce a `SyncSelectionProof` for `slot` signed by the secret key of `validator_pubkey`.
fn produce_sync_selection_proof(
&self,
validator_pubkey: &PublicKeyBytes,
slot: Slot,
subnet_id: SyncSubnetId,
) -> impl Future<Output = Result<SyncSelectionProof, Error<Self::Error>>> + Send;
/// Sign a batch of aggregate and proofs and return results as a stream of batches.
fn sign_aggregate_and_proofs(
self: &Arc<Self>,
aggregates: Vec<AggregateToSign<Self::E>>,
) -> impl Stream<Item = Result<Vec<SignedAggregateAndProof<Self::E>>, Error<Self::Error>>> + Send;
/// Sign a batch of sync committee messages and return results as a stream of batches.
fn sign_sync_committee_signatures(
self: &Arc<Self>,
messages: Vec<SyncMessageToSign>,
) -> impl Stream<Item = Result<Vec<SyncCommitteeMessage>, Error<Self::Error>>> + Send;
/// Sign a batch of sync committee contributions and return results as a stream of batches.
fn sign_sync_committee_contributions(
self: &Arc<Self>,
contributions: Vec<ContributionToSign<Self::E>>,
) -> impl Stream<Item = Result<Vec<SignedContributionAndProof<Self::E>>, Error<Self::Error>>> + Send;
/// Prune the slashing protection database so that it remains performant.
///
/// This function will only do actual pruning periodically, so it should usually be
/// cheap to call. The `first_run` flag can be used to print a more verbose message when pruning
/// runs.
fn prune_slashing_protection_db(&self, current_epoch: Epoch, first_run: bool);
/// Sign an `ExecutionPayloadEnvelope` for Gloas.
fn sign_execution_payload_envelope(
&self,
validator_pubkey: PublicKeyBytes,
envelope: ExecutionPayloadEnvelope<Self::E>,
) -> impl Future<Output = Result<SignedExecutionPayloadEnvelope<Self::E>, Error<Self::Error>>> + Send;
/// Returns `ProposalData` for the provided `pubkey` if it exists in `InitializedValidators`.
/// `ProposalData` fields include defaulting logic described in `get_fee_recipient_defaulting`,
/// `get_gas_limit_defaulting`, and `get_builder_proposals_defaulting`.
fn proposal_data(&self, pubkey: &PublicKeyBytes) -> Option<ProposalData>;
}
#[derive(Debug)]
pub enum UnsignedBlock<E: EthSpec> {
Full(FullBlockContents<E>),
Blinded(BlindedBeaconBlock<E>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum SignedBlock<E: EthSpec> {
Full(PublishBlockRequest<E>),
Blinded(Arc<SignedBlindedBeaconBlock<E>>),
}
/// A wrapper around `PublicKeyBytes` which encodes information about the status of a validator
/// pubkey with regards to doppelganger protection.
#[derive(Debug, PartialEq)]
pub enum DoppelgangerStatus {
/// Doppelganger protection has approved this for signing.
///
/// This is because the service has waited some period of time to
/// detect other instances of this key on the network.
SigningEnabled(PublicKeyBytes),
/// Doppelganger protection is still waiting to detect other instances.
///
/// Do not use this pubkey for signing slashable messages!!
///
/// However, it can safely be used for other non-slashable operations (e.g., collecting duties
/// or subscribing to subnets).
SigningDisabled(PublicKeyBytes),
/// This pubkey is unknown to the doppelganger service.
///
/// This represents a serious internal error in the program. This validator will be permanently
/// disabled!
UnknownToDoppelganger(PublicKeyBytes),
}
impl DoppelgangerStatus {
/// Only return a pubkey if it is explicitly safe for doppelganger protection.
///
/// If `Some(pubkey)` is returned, doppelganger has declared it safe for signing.
///
/// ## Note
///
/// "Safe" is only best-effort by doppelganger. There is no guarantee that a doppelganger
/// doesn't exist.
pub fn only_safe(self) -> Option<PublicKeyBytes> {
match self {
DoppelgangerStatus::SigningEnabled(pubkey) => Some(pubkey),
DoppelgangerStatus::SigningDisabled(_) => None,
DoppelgangerStatus::UnknownToDoppelganger(_) => None,
}
}
/// Returns a key regardless of whether or not doppelganger has approved it. Such a key might be
/// used for signing non-slashable messages, duties collection or other activities.
///
/// If the validator is unknown to doppelganger then `None` will be returned.
pub fn ignored(self) -> Option<PublicKeyBytes> {
match self {
DoppelgangerStatus::SigningEnabled(pubkey) => Some(pubkey),
DoppelgangerStatus::SigningDisabled(pubkey) => Some(pubkey),
DoppelgangerStatus::UnknownToDoppelganger(_) => None,
}
}
/// Only return a pubkey if it will not be used for signing due to doppelganger detection.
pub fn only_unsafe(self) -> Option<PublicKeyBytes> {
match self {
DoppelgangerStatus::SigningEnabled(_) => None,
DoppelgangerStatus::SigningDisabled(pubkey) => Some(pubkey),
DoppelgangerStatus::UnknownToDoppelganger(pubkey) => Some(pubkey),
}
}
}