mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 12:47:05 +00:00
Resolve merge conflicts
This commit is contained in:
@@ -19,5 +19,7 @@ types = { workspace = true }
|
||||
validator_store = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
arbitrary = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
logging = { workspace = true }
|
||||
types = { workspace = true, features = ["arbitrary"] }
|
||||
|
||||
@@ -598,14 +598,12 @@ impl DoppelgangerService {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use arbitrary::Arbitrary;
|
||||
use futures::executor::block_on;
|
||||
use slot_clock::TestingSlotClock;
|
||||
use std::future;
|
||||
use std::time::Duration;
|
||||
use types::{
|
||||
MainnetEthSpec,
|
||||
test_utils::{SeedableRng, TestRandom, XorShiftRng},
|
||||
};
|
||||
use types::MainnetEthSpec;
|
||||
use validator_store::DoppelgangerStatus;
|
||||
|
||||
const DEFAULT_VALIDATORS: usize = 8;
|
||||
@@ -641,12 +639,12 @@ mod test {
|
||||
|
||||
impl TestBuilder {
|
||||
fn build(self) -> TestScenario {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let mut u = types::test_utils::test_unstructured();
|
||||
let slot_clock = TestingSlotClock::new(Slot::new(0), GENESIS_TIME, SLOT_DURATION);
|
||||
|
||||
TestScenario {
|
||||
validators: (0..self.validator_count)
|
||||
.map(|_| PublicKeyBytes::random_for_test(&mut rng))
|
||||
.map(|_| PublicKeyBytes::arbitrary(&mut u).unwrap())
|
||||
.collect(),
|
||||
doppelganger: DoppelgangerService::default(),
|
||||
slot_clock,
|
||||
|
||||
@@ -22,11 +22,11 @@ use types::{
|
||||
AbstractExecPayload, Address, AggregateAndProof, Attestation, BeaconBlock, BlindedPayload,
|
||||
ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, ExecutionPayloadEnvelope, Fork,
|
||||
FullPayload, Graffiti, Hash256, InclusionList, PayloadAttestationData,
|
||||
PayloadAttestationMessage, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock,
|
||||
SignedContributionAndProof, SignedExecutionPayloadEnvelope,
|
||||
PayloadAttestationMessage, ProposerPreferences, SelectionProof, SignedAggregateAndProof,
|
||||
SignedBeaconBlock, SignedContributionAndProof, SignedExecutionPayloadEnvelope,
|
||||
SignedExecutionPayloadEnvelopeGloas, SignedExecutionPayloadEnvelopeHeze, SignedInclusionList,
|
||||
SignedRoot, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot,
|
||||
SyncAggregatorSelectionData, SyncCommitteeContribution, SyncCommitteeMessage,
|
||||
SignedProposerPreferences, SignedRoot, SignedValidatorRegistrationData, SignedVoluntaryExit,
|
||||
Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, SyncCommitteeMessage,
|
||||
SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, VoluntaryExit,
|
||||
graffiti::GraffitiString,
|
||||
};
|
||||
@@ -1521,4 +1521,32 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore for LighthouseValidatorS
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn sign_proposer_preferences(
|
||||
&self,
|
||||
validator_pubkey: PublicKeyBytes,
|
||||
preferences: ProposerPreferences,
|
||||
) -> Result<SignedProposerPreferences, Error> {
|
||||
let signing_context = self.signing_context(
|
||||
Domain::ProposerPreferences,
|
||||
preferences.proposal_slot.epoch(E::slots_per_epoch()),
|
||||
);
|
||||
|
||||
let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?;
|
||||
|
||||
let signature = signing_method
|
||||
.get_signature::<E, FullPayload<E>>(
|
||||
SignableMessage::ProposerPreferences(&preferences),
|
||||
signing_context,
|
||||
&self.spec,
|
||||
&self.task_executor,
|
||||
)
|
||||
.await
|
||||
.map_err(Error::SpecificError)?;
|
||||
|
||||
Ok(SignedProposerPreferences {
|
||||
message: preferences,
|
||||
signature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ pub enum SignableMessage<'a, E: EthSpec, Payload: AbstractExecPayload<E> = FullP
|
||||
InclusionList(&'a InclusionList<E>),
|
||||
ExecutionPayloadEnvelope(&'a ExecutionPayloadEnvelope<E>),
|
||||
PayloadAttestationData(&'a PayloadAttestationData),
|
||||
ProposerPreferences(&'a ProposerPreferences),
|
||||
}
|
||||
|
||||
impl<E: EthSpec, Payload: AbstractExecPayload<E>> SignableMessage<'_, E, Payload> {
|
||||
@@ -76,6 +77,7 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> SignableMessage<'_, E, Payload
|
||||
SignableMessage::InclusionList(il) => il.signing_root(domain),
|
||||
SignableMessage::ExecutionPayloadEnvelope(e) => e.signing_root(domain),
|
||||
SignableMessage::PayloadAttestationData(d) => d.signing_root(domain),
|
||||
SignableMessage::ProposerPreferences(p) => p.signing_root(domain),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +248,9 @@ impl SigningMethod {
|
||||
SignableMessage::PayloadAttestationData(d) => {
|
||||
Web3SignerObject::PayloadAttestationData(d)
|
||||
}
|
||||
SignableMessage::ProposerPreferences(p) => {
|
||||
Web3SignerObject::ProposerPreferences(p)
|
||||
}
|
||||
};
|
||||
|
||||
// Determine the Web3Signer message type.
|
||||
|
||||
@@ -23,6 +23,7 @@ pub enum MessageType {
|
||||
// TODO(gloas) verify w/ web3signer specs
|
||||
ExecutionPayloadEnvelope,
|
||||
PayloadAttestation,
|
||||
ProposerPreferences,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize)]
|
||||
@@ -83,6 +84,7 @@ pub enum Web3SignerObject<'a, E: EthSpec, Payload: AbstractExecPayload<E>> {
|
||||
InclusionList(&'a InclusionList<E>),
|
||||
ExecutionPayloadEnvelope(&'a ExecutionPayloadEnvelope<E>),
|
||||
PayloadAttestationData(&'a PayloadAttestationData),
|
||||
ProposerPreferences(&'a ProposerPreferences),
|
||||
}
|
||||
|
||||
impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> Web3SignerObject<'a, E, Payload> {
|
||||
@@ -156,6 +158,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> Web3SignerObject<'a, E, Pa
|
||||
Web3SignerObject::InclusionList(_) => MessageType::InclusionList,
|
||||
Web3SignerObject::ExecutionPayloadEnvelope(_) => MessageType::ExecutionPayloadEnvelope,
|
||||
Web3SignerObject::PayloadAttestationData(_) => MessageType::PayloadAttestation,
|
||||
Web3SignerObject::ProposerPreferences(_) => MessageType::ProposerPreferences,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ use validator_services::{
|
||||
latency_service,
|
||||
payload_attestation_service::PayloadAttestationService,
|
||||
preparation_service::{PreparationService, PreparationServiceBuilder},
|
||||
proposer_preferences_service::ProposerPreferencesService,
|
||||
sync_committee_service::SyncCommitteeService,
|
||||
};
|
||||
use validator_store::ValidatorStore as ValidatorStoreTrait;
|
||||
@@ -88,6 +89,8 @@ pub struct ProductionValidatorClient<E: EthSpec> {
|
||||
sync_committee_service: SyncCommitteeService<ValidatorStore<E>, SystemTimeSlotClock>,
|
||||
inclusion_list_service: InclusionListService<ValidatorStore<E>, SystemTimeSlotClock>,
|
||||
payload_attestation_service: PayloadAttestationService<ValidatorStore<E>, SystemTimeSlotClock>,
|
||||
proposer_preferences_service:
|
||||
ProposerPreferencesService<ValidatorStore<E>, SystemTimeSlotClock>,
|
||||
doppelganger_service: Option<Arc<DoppelgangerService>>,
|
||||
preparation_service: PreparationService<ValidatorStore<E>, SystemTimeSlotClock>,
|
||||
validator_store: Arc<ValidatorStore<E>>,
|
||||
@@ -585,6 +588,15 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
|
||||
context.eth2_config.spec.clone(),
|
||||
);
|
||||
|
||||
let proposer_preferences_service = ProposerPreferencesService::new(
|
||||
duties_service.clone(),
|
||||
validator_store.clone(),
|
||||
slot_clock.clone(),
|
||||
beacon_nodes.clone(),
|
||||
context.executor.clone(),
|
||||
context.eth2_config.spec.clone(),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
context,
|
||||
duties_service,
|
||||
@@ -593,6 +605,7 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
|
||||
sync_committee_service,
|
||||
inclusion_list_service,
|
||||
payload_attestation_service,
|
||||
proposer_preferences_service,
|
||||
doppelganger_service,
|
||||
preparation_service,
|
||||
validator_store,
|
||||
@@ -674,6 +687,11 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
|
||||
.clone()
|
||||
.start_update_service()
|
||||
.map_err(|e| format!("Unable to start payload attestation service: {}", e))?;
|
||||
|
||||
self.proposer_preferences_service
|
||||
.clone()
|
||||
.start_update_service()
|
||||
.map_err(|e| format!("Unable to start proposer preferences service: {}", e))?;
|
||||
}
|
||||
|
||||
self.preparation_service
|
||||
|
||||
@@ -6,5 +6,6 @@ pub mod latency_service;
|
||||
pub mod notifier_service;
|
||||
pub mod payload_attestation_service;
|
||||
pub mod preparation_service;
|
||||
pub mod proposer_preferences_service;
|
||||
pub mod sync;
|
||||
pub mod sync_committee_service;
|
||||
|
||||
@@ -139,14 +139,22 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> PayloadAttestationServ
|
||||
beacon_node
|
||||
.get_validator_payload_attestation_data(slot)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get payload attestation data: {e:?}"))
|
||||
.map(|resp| resp.into_data())
|
||||
.map(|opt| opt.map(|resp| resp.into_data()))
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(data) => data,
|
||||
Ok(Some(data)) => data,
|
||||
Ok(None) => {
|
||||
// Per the consensus spec, validators should not submit a
|
||||
// payload attestation when no block has been seen for the slot.
|
||||
debug!(
|
||||
%slot,
|
||||
"No block received for slot, skipping payload attestation"
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
crit!(
|
||||
error!(
|
||||
error = %e,
|
||||
%slot,
|
||||
"Failed to produce payload attestation data"
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
use crate::duties_service::DutiesService;
|
||||
use beacon_node_fallback::BeaconNodeFallback;
|
||||
use slot_clock::SlotClock;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use types::{ChainSpec, Epoch, EthSpec, ForkName, ProposerPreferences};
|
||||
use validator_store::ValidatorStore;
|
||||
|
||||
pub struct Inner<S, T> {
|
||||
duties_service: Arc<DutiesService<S, T>>,
|
||||
validator_store: Arc<S>,
|
||||
slot_clock: T,
|
||||
beacon_nodes: Arc<BeaconNodeFallback<T>>,
|
||||
executor: TaskExecutor,
|
||||
chain_spec: Arc<ChainSpec>,
|
||||
}
|
||||
|
||||
pub struct ProposerPreferencesService<S, T> {
|
||||
inner: Arc<Inner<S, T>>,
|
||||
}
|
||||
|
||||
impl<S, T> Clone for ProposerPreferencesService<S, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Deref for ProposerPreferencesService<S, T> {
|
||||
type Target = Inner<S, T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ValidatorStore + 'static, T: SlotClock + 'static> ProposerPreferencesService<S, T> {
|
||||
pub fn new(
|
||||
duties_service: Arc<DutiesService<S, T>>,
|
||||
validator_store: Arc<S>,
|
||||
slot_clock: T,
|
||||
beacon_nodes: Arc<BeaconNodeFallback<T>>,
|
||||
executor: TaskExecutor,
|
||||
chain_spec: Arc<ChainSpec>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Inner {
|
||||
duties_service,
|
||||
validator_store,
|
||||
slot_clock,
|
||||
beacon_nodes,
|
||||
executor,
|
||||
chain_spec,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_update_service(self) -> Result<(), String> {
|
||||
let slot_duration = self.chain_spec.get_slot_duration();
|
||||
info!("Proposer preferences service started");
|
||||
|
||||
let executor = self.executor.clone();
|
||||
|
||||
let interval_fut = async move {
|
||||
loop {
|
||||
let Some(current_slot) = self.slot_clock.now() else {
|
||||
error!("Failed to read slot clock");
|
||||
sleep(slot_duration).await;
|
||||
continue;
|
||||
};
|
||||
|
||||
if !self
|
||||
.chain_spec
|
||||
.fork_name_at_slot::<S::E>(current_slot)
|
||||
.gloas_enabled()
|
||||
{
|
||||
let duration_to_next_epoch = self
|
||||
.slot_clock
|
||||
.duration_to_next_epoch(S::E::slots_per_epoch())
|
||||
.unwrap_or_else(|| slot_duration * S::E::slots_per_epoch() as u32);
|
||||
sleep(duration_to_next_epoch).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
let current_epoch = current_slot.epoch(S::E::slots_per_epoch());
|
||||
let fork_name = self.chain_spec.fork_name_at_slot::<S::E>(current_slot);
|
||||
self.publish_proposer_preferences(current_epoch, fork_name)
|
||||
.await;
|
||||
|
||||
let duration_to_next_epoch = self
|
||||
.slot_clock
|
||||
.duration_to_next_epoch(S::E::slots_per_epoch())
|
||||
.unwrap_or_else(|| slot_duration * S::E::slots_per_epoch() as u32);
|
||||
sleep(duration_to_next_epoch).await;
|
||||
}
|
||||
};
|
||||
|
||||
executor.spawn(interval_fut, "proposer_preferences_service");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn publish_proposer_preferences(&self, current_epoch: Epoch, fork_name: ForkName) {
|
||||
let (dependent_root, duties) = {
|
||||
let proposers = self.duties_service.proposers.read();
|
||||
match proposers.get(¤t_epoch) {
|
||||
Some((root, duties)) => (*root, duties.clone()),
|
||||
None => return,
|
||||
}
|
||||
};
|
||||
|
||||
let preferences_to_sign: Vec<_> = {
|
||||
let mut result = vec![];
|
||||
for duty in &duties {
|
||||
let Some(proposal_data) = self.validator_store.proposal_data(&duty.pubkey) else {
|
||||
warn!(
|
||||
validator = ?duty.pubkey,
|
||||
"Missing proposal data for proposer preferences"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(fee_recipient) = proposal_data.fee_recipient else {
|
||||
warn!(
|
||||
validator = ?duty.pubkey,
|
||||
"Missing fee recipient for proposer preferences"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
result.push((
|
||||
duty.pubkey,
|
||||
ProposerPreferences {
|
||||
dependent_root,
|
||||
proposal_slot: duty.slot,
|
||||
validator_index: duty.validator_index,
|
||||
fee_recipient,
|
||||
gas_limit: proposal_data.gas_limit,
|
||||
},
|
||||
));
|
||||
}
|
||||
result
|
||||
};
|
||||
|
||||
if preferences_to_sign.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
debug!(
|
||||
%current_epoch,
|
||||
count = preferences_to_sign.len(),
|
||||
"Signing proposer preferences"
|
||||
);
|
||||
|
||||
let mut signed = Vec::with_capacity(preferences_to_sign.len());
|
||||
for (pubkey, preferences) in preferences_to_sign {
|
||||
match self
|
||||
.validator_store
|
||||
.sign_proposer_preferences(pubkey, preferences)
|
||||
.await
|
||||
{
|
||||
Ok(signed_prefs) => signed.push(signed_prefs),
|
||||
Err(e) => {
|
||||
error!(
|
||||
error = ?e,
|
||||
validator = ?pubkey,
|
||||
"Failed to sign proposer preferences"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if signed.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let count = signed.len();
|
||||
let signed = Arc::new(signed);
|
||||
let result = self
|
||||
.beacon_nodes
|
||||
.first_success(|beacon_node| {
|
||||
let signed = signed.clone();
|
||||
async move {
|
||||
match beacon_node
|
||||
.post_validator_proposer_preferences_ssz(&signed, fork_name)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(()),
|
||||
Err(ssz_err) => {
|
||||
debug!(error = ?ssz_err, "SSZ publish failed, falling back to JSON");
|
||||
beacon_node
|
||||
.post_validator_proposer_preferences(&signed, fork_name)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
format!("Failed to publish proposer preferences: {e:?}")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(()) => {
|
||||
info!(
|
||||
%current_epoch,
|
||||
%count,
|
||||
"Successfully published proposer preferences"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
error = %e,
|
||||
%current_epoch,
|
||||
"Failed to publish proposer preferences"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,11 @@ use std::sync::Arc;
|
||||
use types::{
|
||||
Address, Attestation, AttestationError, BlindedBeaconBlock, Epoch, EthSpec,
|
||||
ExecutionPayloadEnvelope, Graffiti, Hash256, InclusionList, PayloadAttestationData,
|
||||
PayloadAttestationMessage, SelectionProof, SignedAggregateAndProof, SignedBlindedBeaconBlock,
|
||||
SignedContributionAndProof, SignedExecutionPayloadEnvelope, SignedInclusionList,
|
||||
SignedValidatorRegistrationData, Slot, SyncCommitteeContribution, SyncCommitteeMessage,
|
||||
SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
|
||||
PayloadAttestationMessage, ProposerPreferences, SelectionProof, SignedAggregateAndProof,
|
||||
SignedBlindedBeaconBlock, SignedContributionAndProof, SignedExecutionPayloadEnvelope,
|
||||
SignedInclusionList, SignedProposerPreferences, SignedValidatorRegistrationData, Slot,
|
||||
SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId,
|
||||
ValidatorRegistrationData,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -219,6 +220,13 @@ pub trait ValidatorStore: Send + Sync {
|
||||
data: PayloadAttestationData,
|
||||
) -> impl Future<Output = Result<PayloadAttestationMessage, Error<Self::Error>>> + Send;
|
||||
|
||||
/// Sign a `ProposerPreferences` message.
|
||||
fn sign_proposer_preferences(
|
||||
&self,
|
||||
validator_pubkey: PublicKeyBytes,
|
||||
preferences: ProposerPreferences,
|
||||
) -> impl Future<Output = Result<SignedProposerPreferences, 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`.
|
||||
|
||||
Reference in New Issue
Block a user