From 65eaf01942c72e593eb2e943b2d9a5d24a4d6120 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 9 Mar 2022 06:36:38 +0000 Subject: [PATCH] VC: avoid sending fee recipients until just before merge (#3064) ## Issue Addressed Presently if the VC is configured with a fee recipient it will error out when sending fee-recipient preparations to a beacon node that doesn't yet support the API: ``` Mar 08 22:23:36.236 ERRO Unable to publish proposer preparation error: All endpoints failed https://eth2-beacon-prater.infura.io/ => RequestFailed(StatusCode(404)), service: preparation ``` This doesn't affect other VC duties, but could be a source of anxiety for users trying to do the right thing and configure their fee recipients in advance. ## Proposed Changes Change the preparation service to only send preparations if the current slot is later than 2 epochs before the Bellatrix hard fork epoch. ## Additional Info I've tagged this v2.1.4 as I think it's a small change that's worth having for the next release --- validator_client/src/preparation_service.rs | 47 +++++++++++++-------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/validator_client/src/preparation_service.rs b/validator_client/src/preparation_service.rs index e532bd246..ad04717cc 100644 --- a/validator_client/src/preparation_service.rs +++ b/validator_client/src/preparation_service.rs @@ -11,6 +11,9 @@ use std::sync::Arc; use tokio::time::{sleep, Duration}; use types::{Address, ChainSpec, EthSpec, ProposerPreparationData}; +/// Number of epochs before the Bellatrix hard fork to begin posting proposer preparations. +const PROPOSER_PREPARATION_LOOKAHEAD_EPOCHS: u64 = 2; + /// Builds an `PreparationService`. pub struct PreparationServiceBuilder { validator_store: Option>>, @@ -122,15 +125,9 @@ impl PreparationService { let log = self.context.log().clone(); let slot_duration = Duration::from_secs(spec.seconds_per_slot); - 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")?; - info!( log, "Proposer preparation service started"; - "next_update_millis" => duration_to_next_epoch.as_millis() ); let executor = self.context.executor.clone(); @@ -138,17 +135,19 @@ impl PreparationService { let interval_fut = async move { loop { - // Poll the endpoint immediately to ensure fee recipients are received. - self.prepare_proposers_and_publish(&spec) - .await - .map_err(|e| { - error!( - log, - "Error during proposer preparation"; - "error" => format!("{:?}", e), - ) - }) - .unwrap_or(()); + if self.should_publish_at_current_slot(&spec) { + // Poll the endpoint immediately to ensure fee recipients are received. + self.prepare_proposers_and_publish(&spec) + .await + .map_err(|e| { + error!( + log, + "Error during proposer preparation"; + "error" => ?e, + ) + }) + .unwrap_or(()); + } if let Some(duration_to_next_slot) = self.slot_clock.duration_to_next_slot() { sleep(duration_to_next_slot).await; @@ -164,6 +163,20 @@ impl PreparationService { Ok(()) } + /// Return `true` if the current slot is close to or past the Bellatrix fork epoch. + /// + /// This avoids spamming the BN with preparations before the Bellatrix fork epoch, which may + /// cause errors if it doesn't support the preparation API. + fn should_publish_at_current_slot(&self, spec: &ChainSpec) -> bool { + let current_epoch = self + .slot_clock + .now() + .map_or(E::genesis_epoch(), |slot| slot.epoch(E::slots_per_epoch())); + spec.bellatrix_fork_epoch.map_or(false, |fork_epoch| { + current_epoch + PROPOSER_PREPARATION_LOOKAHEAD_EPOCHS >= fork_epoch + }) + } + /// Prepare proposer preparations and send to beacon node async fn prepare_proposers_and_publish(&self, spec: &ChainSpec) -> Result<(), String> { let preparation_data = self.collect_preparation_data(spec);