merge with upstream

This commit is contained in:
realbigsean
2022-12-01 11:13:07 -05:00
135 changed files with 4516 additions and 1734 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "beacon_node"
version = "3.2.1"
version = "3.3.0"
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com"]
edition = "2021"
+507 -295
View File
@@ -20,8 +20,7 @@ use crate::errors::{BeaconChainError as Error, BlockProductionError};
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
use crate::eth1_finalization_cache::{Eth1FinalizationCache, Eth1FinalizationData};
use crate::events::ServerSentEventHandler;
use crate::execution_payload::get_execution_payload;
use crate::execution_payload::PreparePayloadHandle;
use crate::execution_payload::{get_execution_payload, NotifyExecutionLayer, PreparePayloadHandle};
use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult};
use crate::head_tracker::HeadTracker;
use crate::historical_blocks::HistoricalBlockError;
@@ -80,7 +79,7 @@ use ssz::Encode;
#[cfg(feature = "withdrawals")]
use state_processing::per_block_processing::get_expected_withdrawals;
use state_processing::{
common::{get_attesting_indices_from_state, get_indexed_attestation},
common::get_attesting_indices_from_state,
per_block_processing,
per_block_processing::{
errors::AttestationValidationError, verify_attestation_for_block_inclusion,
@@ -1047,6 +1046,46 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(self.store.get_state(state_root, slot)?)
}
/// Run a function with mutable access to a state for `block_root`.
///
/// The primary purpose of this function is to borrow a state with its tree hash cache
/// from the snapshot cache *without moving it*. This means that calls to this function should
/// be kept to an absolute minimum, because holding the snapshot cache lock has the ability
/// to delay block import.
///
/// If there is no appropriate state in the snapshot cache then one will be loaded from disk.
/// If no state is found on disk then `Ok(None)` will be returned.
///
/// The 2nd parameter to the closure is a bool indicating whether the snapshot cache was used,
/// which can inform logging/metrics.
///
/// NOTE: the medium-term plan is to delete this function and the snapshot cache in favour
/// of `tree-states`, where all caches are CoW and everything is good in the world.
pub fn with_mutable_state_for_block<F, V, Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: &SignedBeaconBlock<T::EthSpec, Payload>,
block_root: Hash256,
f: F,
) -> Result<Option<V>, Error>
where
F: FnOnce(&mut BeaconState<T::EthSpec>, bool) -> Result<V, Error>,
{
if let Some(state) = self
.snapshot_cache
.try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT)
.ok_or(Error::SnapshotCacheLockTimeout)?
.borrow_unadvanced_state_mut(block_root)
{
let cache_hit = true;
f(state, cache_hit).map(Some)
} else if let Some(mut state) = self.get_state(&block.state_root(), Some(block.slot()))? {
let cache_hit = false;
f(&mut state, cache_hit).map(Some)
} else {
Ok(None)
}
}
/// Return the sync committee at `slot + 1` from the canonical chain.
///
/// This is useful when dealing with sync committee messages, because messages are signed
@@ -2387,6 +2426,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self: &Arc<Self>,
chain_segment: Vec<BlockWrapper<T::EthSpec>>,
count_unrealized: CountUnrealized,
notify_execution_layer: NotifyExecutionLayer,
) -> ChainSegmentResult<T::EthSpec> {
let mut imported_blocks = 0;
@@ -2455,6 +2495,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
signature_verified_block.block_root(),
signature_verified_block,
count_unrealized,
notify_execution_layer,
)
.await
{
@@ -2543,6 +2584,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
block_root: Hash256,
unverified_block: B,
count_unrealized: CountUnrealized,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<Hash256, BlockError<T::EthSpec>> {
// Start the Prometheus timer.
let _full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES);
@@ -2555,8 +2597,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// A small closure to group the verification and import errors.
let chain = self.clone();
let import_block = async move {
let execution_pending =
unverified_block.into_execution_pending_block(block_root, &chain)?;
let execution_pending = unverified_block.into_execution_pending_block(
block_root,
&chain,
notify_execution_layer,
)?;
chain
.import_execution_pending_block(execution_pending, count_unrealized)
.await
@@ -2626,6 +2671,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
confirmed_state_roots,
payload_verification_handle,
parent_eth1_finalization_data,
consensus_context,
} = execution_pending_block;
let PayloadVerificationOutcome {
@@ -2679,6 +2725,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
count_unrealized,
parent_block,
parent_eth1_finalization_data,
consensus_context,
)
},
"payload_verification_handle",
@@ -2704,70 +2751,36 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
count_unrealized: CountUnrealized,
parent_block: SignedBlindedBeaconBlock<T::EthSpec>,
parent_eth1_finalization_data: Eth1FinalizationData,
mut consensus_context: ConsensusContext<T::EthSpec>,
) -> Result<Hash256, BlockError<T::EthSpec>> {
// ----------------------------- BLOCK NOT YET ATTESTABLE ----------------------------------
// Everything in this initial section is on the hot path between processing the block and
// being able to attest to it. DO NOT add any extra processing in this initial section
// unless it must run before fork choice.
// -----------------------------------------------------------------------------------------
let current_slot = self.slot()?;
let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch());
let block = signed_block.message();
let post_exec_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_POST_EXEC_PROCESSING);
let attestation_observation_timer =
metrics::start_timer(&metrics::BLOCK_PROCESSING_ATTESTATION_OBSERVATION);
// Iterate through the attestations in the block and register them as an "observed
// attestation". This will stop us from propagating them on the gossip network.
for a in signed_block.message().body().attestations() {
match self.observed_attestations.write().observe_item(a, None) {
// If the observation was successful or if the slot for the attestation was too
// low, continue.
//
// We ignore `SlotTooLow` since this will be very common whilst syncing.
Ok(_) | Err(AttestationObservationError::SlotTooLow { .. }) => {}
Err(e) => return Err(BlockError::BeaconChainError(e.into())),
}
}
metrics::stop_timer(attestation_observation_timer);
// If a slasher is configured, provide the attestations from the block.
if let Some(slasher) = self.slasher.as_ref() {
for attestation in signed_block.message().body().attestations() {
let committee =
state.get_beacon_committee(attestation.data.slot, attestation.data.index)?;
let indexed_attestation = get_indexed_attestation(committee.committee, attestation)
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
slasher.accept_attestation(indexed_attestation);
}
}
// Check against weak subjectivity checkpoint.
self.check_block_against_weak_subjectivity_checkpoint(block, block_root, &state)?;
// If there are new validators in this block, update our pubkey cache.
//
// We perform this _before_ adding the block to fork choice because the pubkey cache is
// used by attestation processing which will only process an attestation if the block is
// known to fork choice. This ordering ensure that the pubkey cache is always up-to-date.
self.validator_pubkey_cache
// The only keys imported here will be ones for validators deposited in this block, because
// the cache *must* already have been updated for the parent block when it was imported.
// Newly deposited validators are not active and their keys are not required by other parts
// of block processing. The reason we do this here and not after making the block attestable
// is so we don't have to think about lock ordering with respect to the fork choice lock.
// There are a bunch of places where we lock both fork choice and the pubkey cache and it
// would be difficult to check that they all lock fork choice first.
let mut kv_store_ops = self
.validator_pubkey_cache
.try_write_for(VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT)
.ok_or(Error::ValidatorPubkeyCacheLockTimeout)?
.import_new_pubkeys(&state)?;
// For the current and next epoch of this state, ensure we have the shuffling from this
// block in our cache.
for relative_epoch in &[RelativeEpoch::Current, RelativeEpoch::Next] {
let shuffling_id = AttestationShufflingId::new(block_root, &state, *relative_epoch)?;
let shuffling_is_cached = self
.shuffling_cache
.try_read_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or(Error::AttestationCacheLockTimeout)?
.contains(&shuffling_id);
if !shuffling_is_cached {
state.build_committee_cache(*relative_epoch, &self.spec)?;
let committee_cache = state.committee_cache(*relative_epoch)?;
self.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or(Error::AttestationCacheLockTimeout)?
.insert_committee_cache(shuffling_id, committee_cache);
}
}
// Apply the state to the attester cache, only if it is from the previous epoch or later.
//
// In a perfect scenario there should be no need to add previous-epoch states to the cache.
@@ -2779,52 +2792,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map_err(BeaconChainError::from)?;
}
// Alias for readability.
let block = signed_block.message();
// Only perform the weak subjectivity check if it was configured.
if let Some(wss_checkpoint) = self.config.weak_subjectivity_checkpoint {
// Note: we're using the finalized checkpoint from the head state, rather than fork
// choice.
//
// We are doing this to ensure that we detect changes in finalization. It's possible
// that fork choice has already been updated to the finalized checkpoint in the block
// we're importing.
let current_head_finalized_checkpoint =
self.canonical_head.cached_head().finalized_checkpoint();
// Compare the existing finalized checkpoint with the incoming block's finalized checkpoint.
let new_finalized_checkpoint = state.finalized_checkpoint();
// This ensures we only perform the check once.
if (current_head_finalized_checkpoint.epoch < wss_checkpoint.epoch)
&& (wss_checkpoint.epoch <= new_finalized_checkpoint.epoch)
{
if let Err(e) =
self.verify_weak_subjectivity_checkpoint(wss_checkpoint, block_root, &state)
{
let mut shutdown_sender = self.shutdown_sender();
crit!(
self.log,
"Weak subjectivity checkpoint verification failed while importing block!";
"block_root" => ?block_root,
"parent_root" => ?block.parent_root(),
"old_finalized_epoch" => ?current_head_finalized_checkpoint.epoch,
"new_finalized_epoch" => ?new_finalized_checkpoint.epoch,
"weak_subjectivity_epoch" => ?wss_checkpoint.epoch,
"error" => ?e,
);
crit!(self.log, "You must use the `--purge-db` flag to clear the database and restart sync. You may be on a hostile network.");
shutdown_sender
.try_send(ShutdownReason::Failure(
"Weak subjectivity checkpoint verification failed. Provided block root is not a checkpoint."
))
.map_err(|err| BlockError::BeaconChainError(BeaconChainError::WeakSubjectivtyShutdownError(err)))?;
return Err(BlockError::WeakSubjectivityConflict);
}
}
}
// Take an exclusive write-lock on fork choice. It's very important prevent deadlocks by
// Take an exclusive write-lock on fork choice. It's very important to prevent deadlocks by
// avoiding taking other locks whilst holding this lock.
let mut fork_choice = self.canonical_head.fork_choice_write_lock();
@@ -2855,77 +2823,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
}
// Allow the validator monitor to learn about a new valid state.
self.validator_monitor
.write()
.process_valid_state(current_slot.epoch(T::EthSpec::slots_per_epoch()), &state);
let validator_monitor = self.validator_monitor.read();
// Register each attester slashing in the block with fork choice.
for attester_slashing in block.body().attester_slashings() {
fork_choice.on_attester_slashing(attester_slashing);
}
// Register each attestation in the block with the fork choice service.
for attestation in block.body().attestations() {
let _fork_choice_attestation_timer =
metrics::start_timer(&metrics::FORK_CHOICE_PROCESS_ATTESTATION_TIMES);
let attestation_target_epoch = attestation.data.target.epoch;
let committee =
state.get_beacon_committee(attestation.data.slot, attestation.data.index)?;
let indexed_attestation = get_indexed_attestation(committee.committee, attestation)
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
match fork_choice.on_attestation(
current_slot,
&indexed_attestation,
AttestationFromBlock::True,
&self.spec,
) {
Ok(()) => Ok(()),
// Ignore invalid attestations whilst importing attestations from a block. The
// block might be very old and therefore the attestations useless to fork choice.
Err(ForkChoiceError::InvalidAttestation(_)) => Ok(()),
Err(e) => Err(BlockError::BeaconChainError(e.into())),
}?;
// To avoid slowing down sync, only register attestations for the
// `observed_block_attesters` if they are from the previous epoch or later.
if attestation_target_epoch + 1 >= current_epoch {
let mut observed_block_attesters = self.observed_block_attesters.write();
for &validator_index in &indexed_attestation.attesting_indices {
if let Err(e) = observed_block_attesters
.observe_validator(attestation_target_epoch, validator_index as usize)
{
debug!(
self.log,
"Failed to register observed block attester";
"error" => ?e,
"epoch" => attestation_target_epoch,
"validator_index" => validator_index,
)
}
}
}
// Only register this with the validator monitor when the block is sufficiently close to
// the current slot.
if VALIDATOR_MONITOR_HISTORIC_EPOCHS as u64 * T::EthSpec::slots_per_epoch()
+ block.slot().as_u64()
>= current_slot.as_u64()
{
match fork_choice.get_block(&block.parent_root()) {
Some(parent_block) => validator_monitor.register_attestation_in_block(
&indexed_attestation,
parent_block.slot,
&self.spec,
),
None => warn!(self.log, "Failed to get parent block"; "slot" => %block.slot()),
}
}
}
// If the block is recent enough and it was not optimistically imported, check to see if it
// becomes the head block. If so, apply it to the early attester cache. This will allow
// attestations to the block without waiting for the block and state to be inserted to the
@@ -2974,56 +2871,28 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
),
}
}
drop(post_exec_timer);
// Register sync aggregate with validator monitor
if let Ok(sync_aggregate) = block.body().sync_aggregate() {
// `SyncCommittee` for the sync_aggregate should correspond to the duty slot
let duty_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
let sync_committee = self.sync_committee_at_epoch(duty_epoch)?;
let participant_pubkeys = sync_committee
.pubkeys
.iter()
.zip(sync_aggregate.sync_committee_bits.iter())
.filter_map(|(pubkey, bit)| bit.then_some(pubkey))
.collect::<Vec<_>>();
// ---------------------------- BLOCK PROBABLY ATTESTABLE ----------------------------------
// Most blocks are now capable of being attested to thanks to the `early_attester_cache`
// cache above. Resume non-essential processing.
// -----------------------------------------------------------------------------------------
validator_monitor.register_sync_aggregate_in_block(
block.slot(),
block.parent_root(),
participant_pubkeys,
);
}
for exit in block.body().voluntary_exits() {
validator_monitor.register_block_voluntary_exit(&exit.message)
}
for slashing in block.body().attester_slashings() {
validator_monitor.register_block_attester_slashing(slashing)
}
for slashing in block.body().proposer_slashings() {
validator_monitor.register_block_proposer_slashing(slashing)
}
drop(validator_monitor);
// Only present some metrics for blocks from the previous epoch or later.
//
// This helps avoid noise in the metrics during sync.
if block.slot().epoch(T::EthSpec::slots_per_epoch()) + 1 >= self.epoch()? {
metrics::observe(
&metrics::OPERATIONS_PER_BLOCK_ATTESTATION,
block.body().attestations().len() as f64,
);
if let Ok(sync_aggregate) = block.body().sync_aggregate() {
metrics::set_gauge(
&metrics::BLOCK_SYNC_AGGREGATE_SET_BITS,
sync_aggregate.num_set_bits() as i64,
);
}
}
self.import_block_update_shuffling_cache(block_root, &mut state)?;
self.import_block_observe_attestations(
block,
&state,
&mut consensus_context,
current_epoch,
);
self.import_block_update_validator_monitor(
block,
&state,
&mut consensus_context,
current_slot,
parent_block.slot(),
);
self.import_block_update_slasher(block, &state, &mut consensus_context);
let db_write_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DB_WRITE);
@@ -3046,7 +2915,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
};
let txn_lock = self.store.hot_db.begin_rw_transaction();
if let Err(e) = self.store.do_atomically(ops) {
kv_store_ops.extend(self.store.convert_to_kv_batch(ops)?);
if let Err(e) = self.store.hot_db.do_atomically(kv_store_ops) {
error!(
self.log,
"Database write failed!";
@@ -3054,6 +2925,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
"error" => ?e,
);
// Clear the early attester cache to prevent attestations which we would later be unable
// to verify due to the failure.
self.early_attester_cache.clear();
// Since the write failed, try to revert the canonical head back to what was stored
// in the database. This attempts to prevent inconsistency between the database and
// fork choice.
@@ -3096,6 +2971,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
eth1_deposit_index: state.eth1_deposit_index(),
};
let current_finalized_checkpoint = state.finalized_checkpoint();
self.snapshot_cache
.try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT)
.ok_or(Error::SnapshotCacheLockTimeout)
@@ -3103,7 +2979,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
snapshot_cache.insert(
BeaconSnapshot {
beacon_state: state,
beacon_block: signed_block,
beacon_block: signed_block.clone(),
beacon_block_root: block_root,
},
None,
@@ -3122,22 +2998,312 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.head_tracker
.register_block(block_root, parent_root, slot);
// Send an event to the `events` endpoint after fully processing the block.
if let Some(event_handler) = self.event_handler.as_ref() {
if event_handler.has_block_subscribers() {
event_handler.register(EventKind::Block(SseBlock {
slot,
block: block_root,
execution_optimistic: payload_verification_status.is_optimistic(),
}));
}
}
metrics::stop_timer(db_write_timer);
metrics::inc_counter(&metrics::BLOCK_PROCESSING_SUCCESSES);
let block_delay_total = get_slot_delay_ms(block_time_imported, slot, &self.slot_clock);
// Update the deposit contract cache.
self.import_block_update_deposit_contract_finalization(
block,
block_root,
current_epoch,
current_finalized_checkpoint,
current_eth1_finalization_data,
parent_eth1_finalization_data,
parent_block.slot(),
);
// Inform the unknown block cache, in case it was waiting on this block.
self.pre_finalization_block_cache
.block_processed(block_root);
self.import_block_update_metrics_and_events(
block,
block_root,
block_time_imported,
payload_verification_status,
current_slot,
);
Ok(block_root)
}
/// Check block's consistentency with any configured weak subjectivity checkpoint.
fn check_block_against_weak_subjectivity_checkpoint(
&self,
block: BeaconBlockRef<T::EthSpec>,
block_root: Hash256,
state: &BeaconState<T::EthSpec>,
) -> Result<(), BlockError<T::EthSpec>> {
// Only perform the weak subjectivity check if it was configured.
let wss_checkpoint = if let Some(checkpoint) = self.config.weak_subjectivity_checkpoint {
checkpoint
} else {
return Ok(());
};
// Note: we're using the finalized checkpoint from the head state, rather than fork
// choice.
//
// We are doing this to ensure that we detect changes in finalization. It's possible
// that fork choice has already been updated to the finalized checkpoint in the block
// we're importing.
let current_head_finalized_checkpoint =
self.canonical_head.cached_head().finalized_checkpoint();
// Compare the existing finalized checkpoint with the incoming block's finalized checkpoint.
let new_finalized_checkpoint = state.finalized_checkpoint();
// This ensures we only perform the check once.
if current_head_finalized_checkpoint.epoch < wss_checkpoint.epoch
&& wss_checkpoint.epoch <= new_finalized_checkpoint.epoch
{
if let Err(e) =
self.verify_weak_subjectivity_checkpoint(wss_checkpoint, block_root, state)
{
let mut shutdown_sender = self.shutdown_sender();
crit!(
self.log,
"Weak subjectivity checkpoint verification failed while importing block!";
"block_root" => ?block_root,
"parent_root" => ?block.parent_root(),
"old_finalized_epoch" => ?current_head_finalized_checkpoint.epoch,
"new_finalized_epoch" => ?new_finalized_checkpoint.epoch,
"weak_subjectivity_epoch" => ?wss_checkpoint.epoch,
"error" => ?e
);
crit!(
self.log,
"You must use the `--purge-db` flag to clear the database and restart sync. \
You may be on a hostile network."
);
shutdown_sender
.try_send(ShutdownReason::Failure(
"Weak subjectivity checkpoint verification failed. \
Provided block root is not a checkpoint.",
))
.map_err(|err| {
BlockError::BeaconChainError(
BeaconChainError::WeakSubjectivtyShutdownError(err),
)
})?;
return Err(BlockError::WeakSubjectivityConflict);
}
}
Ok(())
}
/// Process a block for the validator monitor, including all its constituent messages.
fn import_block_update_validator_monitor(
&self,
block: BeaconBlockRef<T::EthSpec>,
state: &BeaconState<T::EthSpec>,
ctxt: &mut ConsensusContext<T::EthSpec>,
current_slot: Slot,
parent_block_slot: Slot,
) {
// Only register blocks with the validator monitor when the block is sufficiently close to
// the current slot.
if VALIDATOR_MONITOR_HISTORIC_EPOCHS as u64 * T::EthSpec::slots_per_epoch()
+ block.slot().as_u64()
< current_slot.as_u64()
{
return;
}
// Allow the validator monitor to learn about a new valid state.
self.validator_monitor
.write()
.process_valid_state(current_slot.epoch(T::EthSpec::slots_per_epoch()), state);
let validator_monitor = self.validator_monitor.read();
// Sync aggregate.
if let Ok(sync_aggregate) = block.body().sync_aggregate() {
// `SyncCommittee` for the sync_aggregate should correspond to the duty slot
let duty_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
match self.sync_committee_at_epoch(duty_epoch) {
Ok(sync_committee) => {
let participant_pubkeys = sync_committee
.pubkeys
.iter()
.zip(sync_aggregate.sync_committee_bits.iter())
.filter_map(|(pubkey, bit)| bit.then_some(pubkey))
.collect::<Vec<_>>();
validator_monitor.register_sync_aggregate_in_block(
block.slot(),
block.parent_root(),
participant_pubkeys,
);
}
Err(e) => {
warn!(
self.log,
"Unable to fetch sync committee";
"epoch" => duty_epoch,
"purpose" => "validator monitor",
"error" => ?e,
);
}
}
}
// Attestations.
for attestation in block.body().attestations() {
let indexed_attestation = match ctxt.get_indexed_attestation(state, attestation) {
Ok(indexed) => indexed,
Err(e) => {
debug!(
self.log,
"Failed to get indexed attestation";
"purpose" => "validator monitor",
"attestation_slot" => attestation.data.slot,
"error" => ?e,
);
continue;
}
};
validator_monitor.register_attestation_in_block(
indexed_attestation,
parent_block_slot,
&self.spec,
);
}
for exit in block.body().voluntary_exits() {
validator_monitor.register_block_voluntary_exit(&exit.message)
}
for slashing in block.body().attester_slashings() {
validator_monitor.register_block_attester_slashing(slashing)
}
for slashing in block.body().proposer_slashings() {
validator_monitor.register_block_proposer_slashing(slashing)
}
}
/// Iterate through the attestations in the block and register them as "observed".
///
/// This will stop us from propagating them on the gossip network.
fn import_block_observe_attestations(
&self,
block: BeaconBlockRef<T::EthSpec>,
state: &BeaconState<T::EthSpec>,
ctxt: &mut ConsensusContext<T::EthSpec>,
current_epoch: Epoch,
) {
// To avoid slowing down sync, only observe attestations if the block is from the
// previous epoch or later.
if state.current_epoch() + 1 < current_epoch {
return;
}
let _timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_ATTESTATION_OBSERVATION);
for a in block.body().attestations() {
match self.observed_attestations.write().observe_item(a, None) {
// If the observation was successful or if the slot for the attestation was too
// low, continue.
//
// We ignore `SlotTooLow` since this will be very common whilst syncing.
Ok(_) | Err(AttestationObservationError::SlotTooLow { .. }) => {}
Err(e) => {
debug!(
self.log,
"Failed to register observed attestation";
"error" => ?e,
"epoch" => a.data.target.epoch
);
}
}
let indexed_attestation = match ctxt.get_indexed_attestation(state, a) {
Ok(indexed) => indexed,
Err(e) => {
debug!(
self.log,
"Failed to get indexed attestation";
"purpose" => "observation",
"attestation_slot" => a.data.slot,
"error" => ?e,
);
continue;
}
};
let mut observed_block_attesters = self.observed_block_attesters.write();
for &validator_index in &indexed_attestation.attesting_indices {
if let Err(e) = observed_block_attesters
.observe_validator(a.data.target.epoch, validator_index as usize)
{
debug!(
self.log,
"Failed to register observed block attester";
"error" => ?e,
"epoch" => a.data.target.epoch,
"validator_index" => validator_index,
)
}
}
}
}
/// If a slasher is configured, provide the attestations from the block.
fn import_block_update_slasher(
&self,
block: BeaconBlockRef<T::EthSpec>,
state: &BeaconState<T::EthSpec>,
ctxt: &mut ConsensusContext<T::EthSpec>,
) {
if let Some(slasher) = self.slasher.as_ref() {
for attestation in block.body().attestations() {
let indexed_attestation = match ctxt.get_indexed_attestation(state, attestation) {
Ok(indexed) => indexed,
Err(e) => {
debug!(
self.log,
"Failed to get indexed attestation";
"purpose" => "slasher",
"attestation_slot" => attestation.data.slot,
"error" => ?e,
);
continue;
}
};
slasher.accept_attestation(indexed_attestation.clone());
}
}
}
fn import_block_update_metrics_and_events(
&self,
block: BeaconBlockRef<T::EthSpec>,
block_root: Hash256,
block_time_imported: Duration,
payload_verification_status: PayloadVerificationStatus,
current_slot: Slot,
) {
// Only present some metrics for blocks from the previous epoch or later.
//
// This helps avoid noise in the metrics during sync.
if block.slot() + 2 * T::EthSpec::slots_per_epoch() >= current_slot {
metrics::observe(
&metrics::OPERATIONS_PER_BLOCK_ATTESTATION,
block.body().attestations().len() as f64,
);
if let Ok(sync_aggregate) = block.body().sync_aggregate() {
metrics::set_gauge(
&metrics::BLOCK_SYNC_AGGREGATE_SET_BITS,
sync_aggregate.num_set_bits() as i64,
);
}
}
let block_delay_total =
get_slot_delay_ms(block_time_imported, block.slot(), &self.slot_clock);
// Do not write to the cache for blocks older than 2 epochs, this helps reduce writes to
// the cache during sync.
@@ -3169,62 +3335,105 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
);
}
// Do not write to eth1 finalization cache for blocks older than 5 epochs
// this helps reduce noise during sync
if block_delay_total
< self.slot_clock.slot_duration() * 5 * (T::EthSpec::slots_per_epoch() as u32)
{
let parent_block_epoch = parent_block.slot().epoch(T::EthSpec::slots_per_epoch());
if parent_block_epoch < current_epoch {
// we've crossed epoch boundary, store Eth1FinalizationData
let (checkpoint, eth1_finalization_data) =
if current_slot % T::EthSpec::slots_per_epoch() == 0 {
// current block is the checkpoint
(
Checkpoint {
epoch: current_epoch,
root: block_root,
},
current_eth1_finalization_data,
)
} else {
// parent block is the checkpoint
(
Checkpoint {
epoch: current_epoch,
root: parent_block.canonical_root(),
},
parent_eth1_finalization_data,
)
};
if let Some(event_handler) = self.event_handler.as_ref() {
if event_handler.has_block_subscribers() {
event_handler.register(EventKind::Block(SseBlock {
slot: block.slot(),
block: block_root,
execution_optimistic: payload_verification_status.is_optimistic(),
}));
}
}
}
if let Some(finalized_eth1_data) = self
.eth1_finalization_cache
.try_write_for(ETH1_FINALIZATION_CACHE_LOCK_TIMEOUT)
.and_then(|mut cache| {
cache.insert(checkpoint, eth1_finalization_data);
cache.finalize(&current_finalized_checkpoint)
})
{
if let Some(eth1_chain) = self.eth1_chain.as_ref() {
let finalized_deposit_count = finalized_eth1_data.deposit_count;
eth1_chain.finalize_eth1_data(finalized_eth1_data);
debug!(
self.log,
"called eth1_chain.finalize_eth1_data()";
"epoch" => current_finalized_checkpoint.epoch,
"deposit count" => finalized_deposit_count,
);
}
fn import_block_update_shuffling_cache(
&self,
block_root: Hash256,
state: &mut BeaconState<T::EthSpec>,
) -> Result<(), BlockError<T::EthSpec>> {
// For the current and next epoch of this state, ensure we have the shuffling from this
// block in our cache.
for relative_epoch in [RelativeEpoch::Current, RelativeEpoch::Next] {
let shuffling_id = AttestationShufflingId::new(block_root, state, relative_epoch)?;
let shuffling_is_cached = self
.shuffling_cache
.try_read_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or(Error::AttestationCacheLockTimeout)?
.contains(&shuffling_id);
if !shuffling_is_cached {
state.build_committee_cache(relative_epoch, &self.spec)?;
let committee_cache = state.committee_cache(relative_epoch)?;
self.shuffling_cache
.try_write_for(ATTESTATION_CACHE_LOCK_TIMEOUT)
.ok_or(Error::AttestationCacheLockTimeout)?
.insert_committee_cache(shuffling_id, committee_cache);
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn import_block_update_deposit_contract_finalization(
&self,
block: BeaconBlockRef<T::EthSpec>,
block_root: Hash256,
current_epoch: Epoch,
current_finalized_checkpoint: Checkpoint,
current_eth1_finalization_data: Eth1FinalizationData,
parent_eth1_finalization_data: Eth1FinalizationData,
parent_block_slot: Slot,
) {
// Do not write to eth1 finalization cache for blocks older than 5 epochs.
if block.slot().epoch(T::EthSpec::slots_per_epoch()) + 5 < current_epoch {
return;
}
let parent_block_epoch = parent_block_slot.epoch(T::EthSpec::slots_per_epoch());
if parent_block_epoch < current_epoch {
// we've crossed epoch boundary, store Eth1FinalizationData
let (checkpoint, eth1_finalization_data) =
if block.slot() % T::EthSpec::slots_per_epoch() == 0 {
// current block is the checkpoint
(
Checkpoint {
epoch: current_epoch,
root: block_root,
},
current_eth1_finalization_data,
)
} else {
// parent block is the checkpoint
(
Checkpoint {
epoch: current_epoch,
root: block.parent_root(),
},
parent_eth1_finalization_data,
)
};
if let Some(finalized_eth1_data) = self
.eth1_finalization_cache
.try_write_for(ETH1_FINALIZATION_CACHE_LOCK_TIMEOUT)
.and_then(|mut cache| {
cache.insert(checkpoint, eth1_finalization_data);
cache.finalize(&current_finalized_checkpoint)
})
{
if let Some(eth1_chain) = self.eth1_chain.as_ref() {
let finalized_deposit_count = finalized_eth1_data.deposit_count;
eth1_chain.finalize_eth1_data(finalized_eth1_data);
debug!(
self.log,
"called eth1_chain.finalize_eth1_data()";
"epoch" => current_finalized_checkpoint.epoch,
"deposit count" => finalized_deposit_count,
);
}
}
}
// Inform the unknown block cache, in case it was waiting on this block.
self.pre_finalization_block_cache
.block_processed(block_root);
Ok(block_root)
}
/// If configured, wait for the fork choice run at the start of the slot to complete.
@@ -3613,10 +3822,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// This will be a lot slower but guards against bugs in block production and can be
// quickly rolled out without a release.
if self.config.paranoid_block_proposal {
let mut tmp_ctxt = ConsensusContext::new(state.slot());
attestations.retain(|att| {
verify_attestation_for_block_inclusion(
&state,
att,
&mut tmp_ctxt,
VerifySignatures::True,
&self.spec,
)
@@ -4265,19 +4476,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map(|withdrawals_opt| withdrawals_opt.map(|w| w.into()))
.map_err(Error::PrepareProposerFailed)?;
let payload_attributes = PayloadAttributes::V2(PayloadAttributesV2 {
timestamp: self
.slot_clock
let payload_attributes = PayloadAttributes::new(
self.slot_clock
.start_of(prepare_slot)
.ok_or(Error::InvalidSlot(prepare_slot))?
.as_secs(),
prev_randao: head_random,
suggested_fee_recipient: execution_layer
head_random,
execution_layer
.get_suggested_fee_recipient(proposer as u64)
.await,
#[cfg(feature = "withdrawals")]
withdrawals,
});
#[cfg(not(feature = "withdrawals"))]
None,
);
debug!(
self.log,
+162 -106
View File
@@ -46,7 +46,7 @@ use crate::blob_verification::{validate_blob_for_gossip, BlobError};
use crate::eth1_finalization_cache::Eth1FinalizationData;
use crate::execution_payload::{
is_optimistic_candidate_block, validate_execution_payload_for_gossip, validate_merge_block,
AllowOptimisticImport, PayloadNotifier,
AllowOptimisticImport, NotifyExecutionLayer, PayloadNotifier,
};
use crate::kzg_utils;
use crate::snapshot_cache::PreProcessingSnapshot;
@@ -55,15 +55,15 @@ use crate::validator_pubkey_cache::ValidatorPubkeyCache;
use crate::BlockError::BlobValidation;
use crate::{
beacon_chain::{
BeaconForkChoice, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
BeaconForkChoice, ForkChoiceError, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT,
MAXIMUM_GOSSIP_CLOCK_DISPARITY, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
},
metrics, BeaconChain, BeaconChainError, BeaconChainTypes,
};
use derivative::Derivative;
use eth2::types::EventKind;
use execution_layer::PayloadStatus;
use fork_choice::PayloadVerificationStatus;
use fork_choice::{AttestationFromBlock, PayloadVerificationStatus};
use parking_lot::RwLockReadGuard;
use proto_array::Block as ProtoBlock;
use safe_arith::ArithError;
@@ -71,7 +71,7 @@ use slog::{debug, error, warn, Logger};
use slot_clock::SlotClock;
use ssz::Encode;
use state_processing::per_block_processing::eip4844::eip4844::verify_kzg_commitments_against_transactions;
use state_processing::per_block_processing::is_merge_transition_block;
use state_processing::per_block_processing::{errors::IntoWithIndex, is_merge_transition_block};
use state_processing::{
block_signature_verifier::{BlockSignatureVerifier, Error as BlockSignatureVerifierError},
per_block_processing, per_slot_processing,
@@ -576,8 +576,24 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
let pubkey_cache = get_validator_pubkey_cache(chain)?;
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
let mut signature_verified_blocks = Vec::with_capacity(chain_segment.len());
for (block_root, block) in &chain_segment {
signature_verifier.include_all_signatures(block.block(), Some(*block_root), None)?;
let mut consensus_context = ConsensusContext::new(block.slot())
.set_current_block_root(*block_root)
//FIXME(sean) Consider removing this is we pass the blob wrapper everywhere
.set_blobs_sidecar(block.blobs_sidecar());
signature_verifier.include_all_signatures(block.block(), &mut consensus_context)?;
// Save the block and its consensus context. The context will have had its proposer index
// and attesting indices filled in, which can be used to accelerate later block processing.
signature_verified_blocks.push(SignatureVerifiedBlock {
block: block.clone(),
block_root: *block_root,
parent: None,
consensus_context,
});
}
if signature_verifier.verify().is_err() {
@@ -586,23 +602,6 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
drop(pubkey_cache);
let mut signature_verified_blocks = chain_segment
.into_iter()
.map(|(block_root, block)| {
// Proposer index has already been verified above during signature verification.
let consensus_context = ConsensusContext::new(block.slot())
.set_current_block_root(block_root)
.set_proposer_index(block.block().message().proposer_index())
.set_blobs_sidecar(block.blobs_sidecar());
SignatureVerifiedBlock {
block,
block_root,
parent: None,
consensus_context,
}
})
.collect::<Vec<_>>();
if let Some(signature_verified_block) = signature_verified_blocks.first_mut() {
signature_verified_block.parent = Some(parent);
}
@@ -653,6 +652,7 @@ pub struct ExecutionPendingBlock<T: BeaconChainTypes> {
pub parent_block: SignedBeaconBlock<T::EthSpec, BlindedPayload<T::EthSpec>>,
pub parent_eth1_finalization_data: Eth1FinalizationData,
pub confirmed_state_roots: Vec<Hash256>,
pub consensus_context: ConsensusContext<T::EthSpec>,
pub payload_verification_handle: PayloadVerificationHandle<T::EthSpec>,
}
@@ -664,8 +664,9 @@ pub trait IntoExecutionPendingBlock<T: BeaconChainTypes>: Sized {
self,
block_root: Hash256,
chain: &Arc<BeaconChain<T>>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<ExecutionPendingBlock<T>, BlockError<T::EthSpec>> {
self.into_execution_pending_block_slashable(block_root, chain)
self.into_execution_pending_block_slashable(block_root, chain, notify_execution_layer)
.map(|execution_pending| {
// Supply valid block to slasher.
if let Some(slasher) = chain.slasher.as_ref() {
@@ -682,6 +683,7 @@ pub trait IntoExecutionPendingBlock<T: BeaconChainTypes>: Sized {
self,
block_root: Hash256,
chain: &Arc<BeaconChain<T>>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>>;
fn block(&self) -> &SignedBeaconBlock<T::EthSpec>;
@@ -956,10 +958,15 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for GossipVerifiedBlock<T
self,
block_root: Hash256,
chain: &Arc<BeaconChain<T>>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
let execution_pending =
SignatureVerifiedBlock::from_gossip_verified_block_check_slashable(self, chain)?;
execution_pending.into_execution_pending_block_slashable(block_root, chain)
execution_pending.into_execution_pending_block_slashable(
block_root,
chain,
notify_execution_layer,
)
}
fn block(&self) -> &SignedBeaconBlock<T::EthSpec> {
@@ -1002,14 +1009,15 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
signature_verifier.include_all_signatures(block.block(), Some(block_root), None)?;
let mut consensus_context = ConsensusContext::new(block.slot())
.set_current_block_root(block_root)
.set_blobs_sidecar(block.blobs_sidecar());
signature_verifier.include_all_signatures(block.block(), &mut consensus_context)?;
if signature_verifier.verify().is_ok() {
Ok(Self {
consensus_context: ConsensusContext::new(block.slot())
.set_current_block_root(block_root)
.set_proposer_index(block.message().proposer_index())
.set_blobs_sidecar(block.blobs_sidecar()),
consensus_context,
block,
block_root,
parent: Some(parent),
@@ -1054,16 +1062,16 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
// Gossip verification has already checked the proposer index. Use it to check the RANDAO
// signature.
let verified_proposer_index = Some(block.message().proposer_index());
let mut consensus_context = from.consensus_context;
signature_verifier
.include_all_signatures_except_proposal(block.block(), verified_proposer_index)?;
.include_all_signatures_except_proposal(block.block(), &mut consensus_context)?;
if signature_verifier.verify().is_ok() {
Ok(Self {
block,
block_root: from.block_root,
parent: Some(parent),
consensus_context: from.consensus_context,
consensus_context,
})
} else {
Err(BlockError::InvalidSignature)
@@ -1091,6 +1099,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for SignatureVerifiedBloc
self,
block_root: Hash256,
chain: &Arc<BeaconChain<T>>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
let header = self.block.block().signed_block_header();
let (parent, block) = if let Some(parent) = self.parent {
@@ -1106,6 +1115,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for SignatureVerifiedBloc
parent,
self.consensus_context,
chain,
notify_execution_layer,
)
.map_err(|e| BlockSlashInfo::SignatureValid(header, e))
}
@@ -1122,6 +1132,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for Arc<SignedBeaconBlock
self,
block_root: Hash256,
chain: &Arc<BeaconChain<T>>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
// Perform an early check to prevent wasting time on irrelevant blocks.
let block_root = check_block_relevancy(&self, block_root, chain)
@@ -1132,7 +1143,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for Arc<SignedBeaconBlock
block_root,
chain,
)?
.into_execution_pending_block_slashable(block_root, chain)
.into_execution_pending_block_slashable(block_root, chain, notify_execution_layer)
}
fn block(&self) -> &SignedBeaconBlock<T::EthSpec> {
@@ -1147,6 +1158,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for BlockWrapper<T::EthSp
self,
block_root: Hash256,
chain: &Arc<BeaconChain<T>>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError<T::EthSpec>>> {
// Perform an early check to prevent wasting time on irrelevant blocks.
let block_root = check_block_relevancy(self.block(), block_root, chain).map_err(|e| {
@@ -1154,7 +1166,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for BlockWrapper<T::EthSp
})?;
SignatureVerifiedBlock::check_slashable(self, block_root, chain)?
.into_execution_pending_block_slashable(block_root, chain)
.into_execution_pending_block_slashable(block_root, chain, notify_execution_layer)
}
fn block(&self) -> &SignedBeaconBlock<T::EthSpec> {
@@ -1176,6 +1188,7 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
parent: PreProcessingSnapshot<T::EthSpec>,
mut consensus_context: ConsensusContext<T::EthSpec>,
chain: &Arc<BeaconChain<T>>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<Self, BlockError<T::EthSpec>> {
if let Some(parent) = chain
.canonical_head
@@ -1212,6 +1225,79 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
check_block_relevancy(block.block(), block_root, chain)?;
// Define a future that will verify the execution payload with an execution engine.
//
// We do this as early as possible so that later parts of this function can run in parallel
// with the payload verification.
let payload_notifier = PayloadNotifier::new(
chain.clone(),
block.block_cloned(),
&parent.pre_state,
notify_execution_layer,
)?;
let is_valid_merge_transition_block =
is_merge_transition_block(&parent.pre_state, block.message().body());
let payload_verification_future = async move {
let chain = payload_notifier.chain.clone();
let block = payload_notifier.block.clone();
// If this block triggers the merge, check to ensure that it references valid execution
// blocks.
//
// The specification defines this check inside `on_block` in the fork-choice specification,
// however we perform the check here for two reasons:
//
// - There's no point in importing a block that will fail fork choice, so it's best to fail
// early.
// - Doing the check here means we can keep our fork-choice implementation "pure". I.e., no
// calls to remote servers.
if is_valid_merge_transition_block {
validate_merge_block(&chain, block.message(), AllowOptimisticImport::Yes).await?;
};
// The specification declares that this should be run *inside* `per_block_processing`,
// however we run it here to keep `per_block_processing` pure (i.e., no calls to external
// servers).
let payload_verification_status = payload_notifier.notify_new_payload().await?;
// If the payload did not validate or invalidate the block, check to see if this block is
// valid for optimistic import.
if payload_verification_status.is_optimistic() {
let block_hash_opt = block
.message()
.body()
.execution_payload()
.map(|full_payload| full_payload.block_hash());
// Ensure the block is a candidate for optimistic import.
if !is_optimistic_candidate_block(&chain, block.slot(), block.parent_root()).await?
{
warn!(
chain.log,
"Rejecting optimistic block";
"block_hash" => ?block_hash_opt,
"msg" => "the execution engine is not synced"
);
return Err(ExecutionPayloadError::UnverifiedNonOptimisticCandidate.into());
}
}
Ok(PayloadVerificationOutcome {
payload_verification_status,
is_valid_merge_transition_block,
})
};
// Spawn the payload verification future as a new task, but don't wait for it to complete.
// The `payload_verification_future` will be awaited later to ensure verification completed
// successfully.
let payload_verification_handle = chain
.task_executor
.spawn_handle(
payload_verification_future,
"execution_payload_verification",
)
.ok_or(BeaconChainError::RuntimeShutdown)?;
/*
* Advance the given `parent.beacon_state` to the slot of the given `block`.
*/
@@ -1316,79 +1402,11 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
summaries.push(summary);
}
}
metrics::stop_timer(catchup_timer);
let block_slot = block.slot();
let state_current_epoch = state.current_epoch();
// Define a future that will verify the execution payload with an execution engine (but
// don't execute it yet).
let payload_notifier = PayloadNotifier::new(chain.clone(), block.block_cloned(), &state)?;
let is_valid_merge_transition_block =
is_merge_transition_block(&state, block.message().body());
let payload_verification_future = async move {
let chain = payload_notifier.chain.clone();
let block = payload_notifier.block.clone();
// If this block triggers the merge, check to ensure that it references valid execution
// blocks.
//
// The specification defines this check inside `on_block` in the fork-choice specification,
// however we perform the check here for two reasons:
//
// - There's no point in importing a block that will fail fork choice, so it's best to fail
// early.
// - Doing the check here means we can keep our fork-choice implementation "pure". I.e., no
// calls to remote servers.
if is_valid_merge_transition_block {
validate_merge_block(&chain, block.message(), AllowOptimisticImport::Yes).await?;
};
// The specification declares that this should be run *inside* `per_block_processing`,
// however we run it here to keep `per_block_processing` pure (i.e., no calls to external
// servers).
//
// It is important that this function is called *after* `per_slot_processing`, since the
// `randao` may change.
let payload_verification_status = payload_notifier.notify_new_payload().await?;
// If the payload did not validate or invalidate the block, check to see if this block is
// valid for optimistic import.
if payload_verification_status.is_optimistic() {
let block_hash_opt = block
.message()
.body()
.execution_payload()
.map(|full_payload| full_payload.block_hash());
// Ensure the block is a candidate for optimistic import.
if !is_optimistic_candidate_block(&chain, block.slot(), block.parent_root()).await?
{
warn!(
chain.log,
"Rejecting optimistic block";
"block_hash" => ?block_hash_opt,
"msg" => "the execution engine is not synced"
);
return Err(ExecutionPayloadError::UnverifiedNonOptimisticCandidate.into());
}
}
Ok(PayloadVerificationOutcome {
payload_verification_status,
is_valid_merge_transition_block,
})
};
// Spawn the payload verification future as a new task, but don't wait for it to complete.
// The `payload_verification_future` will be awaited later to ensure verification completed
// successfully.
let payload_verification_handle = chain
.task_executor
.spawn_handle(
payload_verification_future,
"execution_payload_verification",
)
.ok_or(BeaconChainError::RuntimeShutdown)?;
// If the block is sufficiently recent, notify the validator monitor.
if let Some(slot) = chain.slot_clock.now() {
let epoch = slot.epoch(T::EthSpec::slots_per_epoch());
@@ -1415,8 +1433,6 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
}
}
metrics::stop_timer(catchup_timer);
/*
* Build the committee caches on the state.
*/
@@ -1506,9 +1522,48 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
});
}
/*
* Apply the block's attestations to fork choice.
*
* We're running in parallel with the payload verification at this point, so this is
* free real estate.
*/
let current_slot = chain.slot()?;
let mut fork_choice = chain.canonical_head.fork_choice_write_lock();
// Register each attester slashing in the block with fork choice.
for attester_slashing in block.message().body().attester_slashings() {
fork_choice.on_attester_slashing(attester_slashing);
}
// Register each attestation in the block with fork choice.
for (i, attestation) in block.message().body().attestations().iter().enumerate() {
let _fork_choice_attestation_timer =
metrics::start_timer(&metrics::FORK_CHOICE_PROCESS_ATTESTATION_TIMES);
let indexed_attestation = consensus_context
.get_indexed_attestation(&state, attestation)
.map_err(|e| BlockError::PerBlockProcessingError(e.into_with_index(i)))?;
match fork_choice.on_attestation(
current_slot,
indexed_attestation,
AttestationFromBlock::True,
&chain.spec,
) {
Ok(()) => Ok(()),
// Ignore invalid attestations whilst importing attestations from a block. The
// block might be very old and therefore the attestations useless to fork choice.
Err(ForkChoiceError::InvalidAttestation(_)) => Ok(()),
Err(e) => Err(BlockError::BeaconChainError(e.into())),
}?;
}
drop(fork_choice);
/*
* Verify kzg proofs and kzg commitments against transactions if required
*/
//FIXME(sean) should this be prior to applying attestions to fork choice above? done in parallel?
if let Some(ref sidecar) = consensus_context.blobs_sidecar() {
if let Some(data_availability_boundary) = chain.data_availability_boundary() {
if block_slot.epoch(T::EthSpec::slots_per_epoch()) > data_availability_boundary {
@@ -1562,6 +1617,7 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
parent_block: parent.beacon_block,
parent_eth1_finalization_data,
confirmed_state_roots,
consensus_context,
payload_verification_handle,
})
}
@@ -45,6 +45,8 @@ pub struct ChainConfig {
pub paranoid_block_proposal: bool,
/// Whether to strictly count unrealized justified votes.
pub count_unrealized_full: CountUnrealizedFull,
/// Optionally set timeout for calls to checkpoint sync endpoint.
pub checkpoint_sync_url_timeout: u64,
}
impl Default for ChainConfig {
@@ -65,6 +67,7 @@ impl Default for ChainConfig {
always_reset_payload_statuses: false,
paranoid_block_proposal: false,
count_unrealized_full: CountUnrealizedFull::default(),
checkpoint_sync_url_timeout: 60,
}
}
}
@@ -12,7 +12,7 @@ use crate::{
BeaconChain, BeaconChainError, BeaconChainTypes, BlockError, BlockProductionError,
ExecutionPayloadError,
};
use execution_layer::{BlockProposalContents, BuilderParams, PayloadStatus};
use execution_layer::{BlockProposalContents, BuilderParams, PayloadAttributes, PayloadStatus};
use fork_choice::{InvalidationOperation, PayloadVerificationStatus};
use proto_array::{Block as ProtoBlock, ExecutionStatus};
use slog::debug;
@@ -38,6 +38,16 @@ pub enum AllowOptimisticImport {
No,
}
/// Signal whether the execution payloads of new blocks should be
/// immediately verified with the EL or imported optimistically without
/// any EL communication.
#[derive(Default, Clone, Copy)]
pub enum NotifyExecutionLayer {
#[default]
Yes,
No,
}
/// Used to await the result of executing payload with a remote EE.
pub struct PayloadNotifier<T: BeaconChainTypes> {
pub chain: Arc<BeaconChain<T>>,
@@ -50,21 +60,28 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
chain: Arc<BeaconChain<T>>,
block: Arc<SignedBeaconBlock<T::EthSpec>>,
state: &BeaconState<T::EthSpec>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<Self, BlockError<T::EthSpec>> {
let payload_verification_status = if is_execution_enabled(state, block.message().body()) {
// Perform the initial stages of payload verification.
//
// We will duplicate these checks again during `per_block_processing`, however these checks
// are cheap and doing them here ensures we protect the execution engine from junk.
partially_verify_execution_payload::<T::EthSpec, FullPayload<T::EthSpec>>(
state,
block.message().execution_payload()?,
&chain.spec,
)
.map_err(BlockError::PerBlockProcessingError)?;
None
} else {
Some(PayloadVerificationStatus::Irrelevant)
let payload_verification_status = match notify_execution_layer {
NotifyExecutionLayer::No => Some(PayloadVerificationStatus::Optimistic),
NotifyExecutionLayer::Yes => {
if is_execution_enabled(state, block.message().body()) {
// Perform the initial stages of payload verification.
//
// We will duplicate these checks again during `per_block_processing`, however these checks
// are cheap and doing them here ensures we protect the execution engine from junk.
partially_verify_execution_payload::<T::EthSpec, FullPayload<T::EthSpec>>(
state,
block.slot(),
block.message().execution_payload()?,
&chain.spec,
)
.map_err(BlockError::PerBlockProcessingError)?;
None
} else {
Some(PayloadVerificationStatus::Irrelevant)
}
}
};
Ok(Self {
@@ -360,7 +377,8 @@ pub fn get_execution_payload<
let spec = &chain.spec;
let current_epoch = state.current_epoch();
let is_merge_transition_complete = is_merge_transition_complete(state);
let timestamp = compute_timestamp_at_slot(state, spec).map_err(BeaconStateError::from)?;
let timestamp =
compute_timestamp_at_slot(state, state.slot(), spec).map_err(BeaconStateError::from)?;
let random = *state.get_randao_mix(current_epoch)?;
let latest_execution_payload_header_block_hash =
state.latest_execution_payload_header()?.block_hash();
@@ -483,20 +501,29 @@ where
.await
.map_err(BlockProductionError::BeaconChain)?;
let suggested_fee_recipient = execution_layer
.get_suggested_fee_recipient(proposer_index)
.await;
let payload_attributes = PayloadAttributes::new(
timestamp,
random,
suggested_fee_recipient,
#[cfg(feature = "withdrawals")]
withdrawals,
#[cfg(not(feature = "withdrawals"))]
None,
);
// Note: the suggested_fee_recipient is stored in the `execution_layer`, it will add this parameter.
//
// This future is not executed here, it's up to the caller to await it.
let block_contents = execution_layer
.get_payload::<Payload>(
parent_hash,
timestamp,
random,
proposer_index,
&payload_attributes,
forkchoice_update_params,
builder_params,
fork,
#[cfg(feature = "withdrawals")]
withdrawals,
&chain.spec,
)
.await
+1
View File
@@ -66,6 +66,7 @@ pub use canonical_head::{CachedHead, CanonicalHead, CanonicalHeadRwLock};
pub use eth1_chain::{Eth1Chain, Eth1ChainBackend};
pub use events::ServerSentEventHandler;
pub use execution_layer::EngineState;
pub use execution_payload::NotifyExecutionLayer;
pub use fork_choice::{ExecutionStatus, ForkchoiceUpdateParameters};
pub use metrics::scrape_for_metrics;
pub use parking_lot;
+5
View File
@@ -64,6 +64,11 @@ lazy_static! {
"beacon_block_processing_state_root_seconds",
"Time spent calculating the state root when processing a block."
);
pub static ref BLOCK_PROCESSING_POST_EXEC_PROCESSING: Result<Histogram> = try_create_histogram_with_buckets(
"beacon_block_processing_post_exec_pre_attestable_seconds",
"Time between finishing execution processing and the block becoming attestable",
linear_buckets(5e-3, 5e-3, 10)
);
pub static ref BLOCK_PROCESSING_DB_WRITE: Result<Histogram> = try_create_histogram(
"beacon_block_processing_db_write_seconds",
"Time spent writing a newly processed block and state to DB"
@@ -298,6 +298,27 @@ impl<T: EthSpec> SnapshotCache<T> {
})
}
/// Borrow the state corresponding to `block_root` if it exists in the cache *unadvanced*.
///
/// Care must be taken not to mutate the state in an invalid way. This function should only
/// be used to mutate the *caches* of the state, for example the tree hash cache when
/// calculating a light client merkle proof.
pub fn borrow_unadvanced_state_mut(
&mut self,
block_root: Hash256,
) -> Option<&mut BeaconState<T>> {
self.snapshots
.iter_mut()
.find(|snapshot| {
// If the pre-state exists then state advance has already taken the state for
// `block_root` and mutated its tree hash cache. Rather than re-building it while
// holding the snapshot cache lock (>1 second), prefer to return `None` from this
// function and force the caller to load it from disk.
snapshot.beacon_block_root == block_root && snapshot.pre_state.is_none()
})
.map(|snapshot| &mut snapshot.beacon_state)
}
/// If there is a snapshot with `block_root`, clone it and return the clone.
pub fn get_cloned(
&self,
+9 -3
View File
@@ -2,7 +2,7 @@ pub use crate::persisted_beacon_chain::PersistedBeaconChain;
pub use crate::{
beacon_chain::{BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY},
migrate::MigratorConfig,
BeaconChainError, ProduceBlockVerification,
BeaconChainError, NotifyExecutionLayer, ProduceBlockVerification,
};
use crate::{
builder::{BeaconChainBuilder, Witness},
@@ -586,7 +586,7 @@ where
pub fn get_timestamp_at_slot(&self) -> u64 {
let state = self.get_current_state();
compute_timestamp_at_slot(&state, &self.spec).unwrap()
compute_timestamp_at_slot(&state, state.slot(), &self.spec).unwrap()
}
pub fn get_current_state_and_root(&self) -> (BeaconState<E>, Hash256) {
@@ -1460,7 +1460,12 @@ where
self.set_current_slot(slot);
let block_hash: SignedBeaconBlockHash = self
.chain
.process_block(block_root, Arc::new(block), CountUnrealized::True)
.process_block(
block_root,
Arc::new(block),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await?
.into();
self.chain.recompute_head_at_current_slot().await;
@@ -1477,6 +1482,7 @@ where
block.canonical_root(),
Arc::new(block),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await?
.into();
@@ -109,6 +109,11 @@ impl EpochSummary {
}
}
pub fn register_block(&mut self, delay: Duration) {
self.blocks += 1;
Self::update_if_lt(&mut self.block_min_delay, delay);
}
pub fn register_unaggregated_attestation(&mut self, delay: Duration) {
self.attestations += 1;
Self::update_if_lt(&mut self.attestation_min_delay, delay);
@@ -613,13 +618,6 @@ impl<T: EthSpec> ValidatorMonitor<T> {
Ok(())
}
fn get_validator_id(&self, validator_index: u64) -> Option<&str> {
self.indices
.get(&validator_index)
.and_then(|pubkey| self.validators.get(pubkey))
.map(|validator| validator.id.as_str())
}
fn get_validator(&self, validator_index: u64) -> Option<&MonitoredValidator> {
self.indices
.get(&validator_index)
@@ -685,7 +683,9 @@ impl<T: EthSpec> ValidatorMonitor<T> {
block_root: Hash256,
slot_clock: &S,
) {
if let Some(id) = self.get_validator_id(block.proposer_index()) {
let epoch = block.slot().epoch(T::slots_per_epoch());
if let Some(validator) = self.get_validator(block.proposer_index()) {
let id = &validator.id;
let delay = get_block_delay_ms(seen_timestamp, block, slot_clock);
metrics::inc_counter_vec(&metrics::VALIDATOR_MONITOR_BEACON_BLOCK_TOTAL, &[src, id]);
@@ -704,6 +704,8 @@ impl<T: EthSpec> ValidatorMonitor<T> {
"src" => src,
"validator" => %id,
);
validator.with_epoch_summary(epoch, |summary| summary.register_block(delay));
}
}
@@ -3,7 +3,8 @@ use crate::{BeaconChainTypes, BeaconStore};
use ssz::{Decode, Encode};
use std::collections::HashMap;
use std::convert::TryInto;
use store::{DBColumn, Error as StoreError, StoreItem};
use std::marker::PhantomData;
use store::{DBColumn, Error as StoreError, KeyValueStore, KeyValueStoreOp, StoreItem};
use types::{BeaconState, Hash256, PublicKey, PublicKeyBytes};
/// Provides a mapping of `validator_index -> validator_publickey`.
@@ -14,21 +15,17 @@ use types::{BeaconState, Hash256, PublicKey, PublicKeyBytes};
/// 2. To reduce the amount of public key _decompression_ required. A `BeaconState` stores public
/// keys in compressed form and they are needed in decompressed form for signature verification.
/// Decompression is expensive when many keys are involved.
///
/// The cache has a `backing` that it uses to maintain a persistent, on-disk
/// copy of itself. This allows it to be restored between process invocations.
pub struct ValidatorPubkeyCache<T: BeaconChainTypes> {
pubkeys: Vec<PublicKey>,
indices: HashMap<PublicKeyBytes, usize>,
pubkey_bytes: Vec<PublicKeyBytes>,
store: BeaconStore<T>,
_phantom: PhantomData<T>,
}
impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
/// Create a new public key cache using the keys in `state.validators`.
///
/// Also creates a new persistence file, returning an error if there is already a file at
/// `persistence_path`.
/// The new cache will be updated with the keys from `state` and immediately written to disk.
pub fn new(
state: &BeaconState<T::EthSpec>,
store: BeaconStore<T>,
@@ -37,10 +34,11 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
pubkeys: vec![],
indices: HashMap::new(),
pubkey_bytes: vec![],
store,
_phantom: PhantomData,
};
cache.import_new_pubkeys(state)?;
let store_ops = cache.import_new_pubkeys(state)?;
store.hot_db.do_atomically(store_ops)?;
Ok(cache)
}
@@ -69,17 +67,19 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
pubkeys,
indices,
pubkey_bytes,
store,
_phantom: PhantomData,
})
}
/// Scan the given `state` and add any new validator public keys.
///
/// Does not delete any keys from `self` if they don't appear in `state`.
///
/// NOTE: The caller *must* commit the returned I/O batch as part of the block import process.
pub fn import_new_pubkeys(
&mut self,
state: &BeaconState<T::EthSpec>,
) -> Result<(), BeaconChainError> {
) -> Result<Vec<KeyValueStoreOp>, BeaconChainError> {
if state.validators().len() > self.pubkeys.len() {
self.import(
state.validators()[self.pubkeys.len()..]
@@ -87,12 +87,12 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
.map(|v| v.pubkey),
)
} else {
Ok(())
Ok(vec![])
}
}
/// Adds zero or more validators to `self`.
fn import<I>(&mut self, validator_keys: I) -> Result<(), BeaconChainError>
fn import<I>(&mut self, validator_keys: I) -> Result<Vec<KeyValueStoreOp>, BeaconChainError>
where
I: Iterator<Item = PublicKeyBytes> + ExactSizeIterator,
{
@@ -100,6 +100,7 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
self.pubkeys.reserve(validator_keys.len());
self.indices.reserve(validator_keys.len());
let mut store_ops = Vec::with_capacity(validator_keys.len());
for pubkey in validator_keys {
let i = self.pubkeys.len();
@@ -107,17 +108,11 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
return Err(BeaconChainError::DuplicateValidatorPublicKey);
}
// The item is written to disk _before_ it is written into
// the local struct.
//
// This means that a pubkey cache read from disk will always be equivalent to or
// _later than_ the cache that was running in the previous instance of Lighthouse.
//
// The motivation behind this ordering is that we do not want to have states that
// reference a pubkey that is not in our cache. However, it's fine to have pubkeys
// that are never referenced in a state.
self.store
.put_item(&DatabasePubkey::key_for_index(i), &DatabasePubkey(pubkey))?;
// Stage the new validator key for writing to disk.
// It will be committed atomically when the block that introduced it is written to disk.
// Notably it is NOT written while the write lock on the cache is held.
// See: https://github.com/sigp/lighthouse/issues/2327
store_ops.push(DatabasePubkey(pubkey).as_kv_store_op(DatabasePubkey::key_for_index(i)));
self.pubkeys.push(
(&pubkey)
@@ -129,7 +124,7 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
self.indices.insert(pubkey, i);
}
Ok(())
Ok(store_ops)
}
/// Get the public key for a validator with index `i`.
@@ -296,9 +291,10 @@ mod test {
// Add some more keypairs.
let (state, keypairs) = get_state(12);
cache
let ops = cache
.import_new_pubkeys(&state)
.expect("should import pubkeys");
store.hot_db.do_atomically(ops).unwrap();
check_cache_get(&cache, &keypairs[..]);
drop(cache);
@@ -3,7 +3,7 @@
use beacon_chain::test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
};
use beacon_chain::{BeaconSnapshot, BlockError, ChainSegmentResult};
use beacon_chain::{BeaconSnapshot, BlockError, ChainSegmentResult, NotifyExecutionLayer};
use fork_choice::CountUnrealized;
use lazy_static::lazy_static;
use logging::test_logger;
@@ -147,14 +147,18 @@ async fn chain_segment_full_segment() {
// Sneak in a little check to ensure we can process empty chain segments.
harness
.chain
.process_chain_segment(vec![], CountUnrealized::True)
.process_chain_segment(vec![], CountUnrealized::True, NotifyExecutionLayer::Yes)
.await
.into_block_error()
.expect("should import empty chain segment");
harness
.chain
.process_chain_segment(blocks.clone(), CountUnrealized::True)
.process_chain_segment(
blocks.clone(),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.into_block_error()
.expect("should import chain segment");
@@ -183,7 +187,11 @@ async fn chain_segment_varying_chunk_size() {
for chunk in blocks.chunks(*chunk_size) {
harness
.chain
.process_chain_segment(chunk.to_vec(), CountUnrealized::True)
.process_chain_segment(
chunk.to_vec(),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.into_block_error()
.unwrap_or_else(|_| panic!("should import chain segment of len {}", chunk_size));
@@ -219,7 +227,7 @@ async fn chain_segment_non_linear_parent_roots() {
matches!(
harness
.chain
.process_chain_segment(blocks, CountUnrealized::True)
.process_chain_segment(blocks, CountUnrealized::True, NotifyExecutionLayer::Yes)
.await
.into_block_error(),
Err(BlockError::NonLinearParentRoots)
@@ -239,7 +247,7 @@ async fn chain_segment_non_linear_parent_roots() {
matches!(
harness
.chain
.process_chain_segment(blocks, CountUnrealized::True)
.process_chain_segment(blocks, CountUnrealized::True, NotifyExecutionLayer::Yes)
.await
.into_block_error(),
Err(BlockError::NonLinearParentRoots)
@@ -270,7 +278,7 @@ async fn chain_segment_non_linear_slots() {
matches!(
harness
.chain
.process_chain_segment(blocks, CountUnrealized::True)
.process_chain_segment(blocks, CountUnrealized::True, NotifyExecutionLayer::Yes)
.await
.into_block_error(),
Err(BlockError::NonLinearSlots)
@@ -291,7 +299,7 @@ async fn chain_segment_non_linear_slots() {
matches!(
harness
.chain
.process_chain_segment(blocks, CountUnrealized::True)
.process_chain_segment(blocks, CountUnrealized::True, NotifyExecutionLayer::Yes)
.await
.into_block_error(),
Err(BlockError::NonLinearSlots)
@@ -317,7 +325,7 @@ async fn assert_invalid_signature(
matches!(
harness
.chain
.process_chain_segment(blocks, CountUnrealized::True)
.process_chain_segment(blocks, CountUnrealized::True, NotifyExecutionLayer::Yes)
.await
.into_block_error(),
Err(BlockError::InvalidSignature)
@@ -339,7 +347,11 @@ async fn assert_invalid_signature(
// imported prior to this test.
let _ = harness
.chain
.process_chain_segment(ancestor_blocks, CountUnrealized::True)
.process_chain_segment(
ancestor_blocks,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await;
harness.chain.recompute_head_at_current_slot().await;
@@ -349,6 +361,7 @@ async fn assert_invalid_signature(
snapshots[block_index].beacon_block.canonical_root(),
snapshots[block_index].beacon_block.clone(),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await;
assert!(
@@ -400,7 +413,11 @@ async fn invalid_signature_gossip_block() {
.collect();
harness
.chain
.process_chain_segment(ancestor_blocks, CountUnrealized::True)
.process_chain_segment(
ancestor_blocks,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.into_block_error()
.expect("should import all blocks prior to the one being tested");
@@ -412,7 +429,8 @@ async fn invalid_signature_gossip_block() {
.process_block(
signed_block.canonical_root(),
Arc::new(signed_block),
CountUnrealized::True
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await,
Err(BlockError::InvalidSignature)
@@ -446,7 +464,7 @@ async fn invalid_signature_block_proposal() {
matches!(
harness
.chain
.process_chain_segment(blocks, CountUnrealized::True)
.process_chain_segment(blocks, CountUnrealized::True, NotifyExecutionLayer::Yes)
.await
.into_block_error(),
Err(BlockError::InvalidSignature)
@@ -644,7 +662,7 @@ async fn invalid_signature_deposit() {
!matches!(
harness
.chain
.process_chain_segment(blocks, CountUnrealized::True)
.process_chain_segment(blocks, CountUnrealized::True, NotifyExecutionLayer::Yes)
.await
.into_block_error(),
Err(BlockError::InvalidSignature)
@@ -725,6 +743,7 @@ async fn block_gossip_verification() {
gossip_verified.block_root,
gossip_verified,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.expect("should import valid gossip verified block");
@@ -996,6 +1015,7 @@ async fn verify_block_for_gossip_slashing_detection() {
verified_block.block_root,
verified_block,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.unwrap();
@@ -1035,6 +1055,7 @@ async fn verify_block_for_gossip_doppelganger_detection() {
verified_block.block_root,
verified_block,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.unwrap();
@@ -1180,7 +1201,8 @@ async fn add_base_block_to_altair_chain() {
.process_block(
base_block.canonical_root(),
Arc::new(base_block.clone()),
CountUnrealized::True
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.err()
@@ -1195,7 +1217,11 @@ async fn add_base_block_to_altair_chain() {
assert!(matches!(
harness
.chain
.process_chain_segment(vec![Arc::new(base_block)], CountUnrealized::True)
.process_chain_segment(
vec![Arc::new(base_block)],
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await,
ChainSegmentResult::Failed {
imported_blocks: 0,
@@ -1313,7 +1339,8 @@ async fn add_altair_block_to_base_chain() {
.process_block(
altair_block.canonical_root(),
Arc::new(altair_block.clone()),
CountUnrealized::True
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.err()
@@ -1328,7 +1355,11 @@ async fn add_altair_block_to_base_chain() {
assert!(matches!(
harness
.chain
.process_chain_segment(vec![Arc::new(altair_block)], CountUnrealized::True)
.process_chain_segment(
vec![Arc::new(altair_block)],
CountUnrealized::True,
NotifyExecutionLayer::Yes
)
.await,
ChainSegmentResult::Failed {
imported_blocks: 0,
+34 -19
View File
@@ -12,17 +12,17 @@ fn verify_execution_payload_chain<T: EthSpec>(chain: &[FullPayload<T>]) {
let mut prev_ep: Option<FullPayload<T>> = None;
for ep in chain {
assert!(*ep != FullPayload::default());
assert!(!ep.is_default_with_empty_roots());
assert!(ep.block_hash() != ExecutionBlockHash::zero());
// Check against previous `ExecutionPayload`.
if let Some(prev_ep) = prev_ep {
assert_eq!(prev_ep.block_hash(), ep.execution_payload.parent_hash);
assert_eq!(prev_ep.block_hash(), ep.execution_payload().parent_hash());
assert_eq!(
prev_ep.execution_payload.block_number + 1,
ep.execution_payload.block_number
prev_ep.execution_payload().block_number() + 1,
ep.execution_payload().block_number()
);
assert!(ep.execution_payload.timestamp > prev_ep.execution_payload.timestamp);
assert!(ep.execution_payload().timestamp() > prev_ep.execution_payload().timestamp());
}
prev_ep = Some(ep.clone());
}
@@ -88,7 +88,7 @@ async fn merge_with_terminal_block_hash_override() {
if i == 0 {
assert_eq!(execution_payload.block_hash(), genesis_pow_block_hash);
}
execution_payloads.push(execution_payload);
execution_payloads.push(execution_payload.into());
}
verify_execution_payload_chain(execution_payloads.as_slice());
@@ -139,9 +139,14 @@ async fn base_altair_merge_with_terminal_block_after_fork() {
let merge_head = &harness.chain.head_snapshot().beacon_block;
assert!(merge_head.as_merge().is_ok());
assert_eq!(merge_head.slot(), merge_fork_slot);
assert_eq!(
*merge_head.message().body().execution_payload().unwrap(),
FullPayload::default()
assert!(
merge_head
.message()
.body()
.execution_payload()
.unwrap()
.is_default_with_empty_roots(),
"Merge head is default payload"
);
/*
@@ -151,13 +156,14 @@ async fn base_altair_merge_with_terminal_block_after_fork() {
harness.extend_slots(1).await;
let one_after_merge_head = &harness.chain.head_snapshot().beacon_block;
assert_eq!(
*one_after_merge_head
assert!(
one_after_merge_head
.message()
.body()
.execution_payload()
.unwrap(),
FullPayload::default()
.unwrap()
.is_default_with_empty_roots(),
"One after merge head is default payload"
);
assert_eq!(one_after_merge_head.slot(), merge_fork_slot + 1);
@@ -184,25 +190,34 @@ async fn base_altair_merge_with_terminal_block_after_fork() {
harness.extend_slots(1).await;
let one_after_merge_head = &harness.chain.head_snapshot().beacon_block;
assert_eq!(
*one_after_merge_head
// FIXME: why is this being tested twice?
assert!(
one_after_merge_head
.message()
.body()
.execution_payload()
.unwrap(),
FullPayload::default()
.unwrap()
.is_default_with_empty_roots(),
"One after merge head is default payload"
);
assert_eq!(one_after_merge_head.slot(), merge_fork_slot + 2);
/*
* Next merge block should include an exec payload.
*/
for _ in 0..4 {
harness.extend_slots(1).await;
let block = &harness.chain.head_snapshot().beacon_block;
execution_payloads.push(block.message().body().execution_payload().unwrap().clone());
execution_payloads.push(
block
.message()
.body()
.execution_payload()
.unwrap()
.clone()
.into(),
);
}
verify_execution_payload_chain(execution_payloads.as_slice());
@@ -7,12 +7,12 @@ use beacon_chain::otb_verification_service::{
use beacon_chain::{
canonical_head::{CachedHead, CanonicalHead},
test_utils::{BeaconChainHarness, EphemeralHarnessType},
BeaconChainError, BlockError, ExecutionPayloadError, StateSkipConfig, WhenSlotSkipped,
INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON,
BeaconChainError, BlockError, ExecutionPayloadError, NotifyExecutionLayer, StateSkipConfig,
WhenSlotSkipped, INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON,
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON,
};
use execution_layer::{
json_structures::{JsonForkchoiceStateV1, JsonPayloadAttributesV1},
json_structures::{JsonForkchoiceStateV1, JsonPayloadAttributes, JsonPayloadAttributesV1},
test_utils::ExecutionBlockGenerator,
ExecutionLayer, ForkchoiceState, PayloadAttributes,
};
@@ -133,7 +133,10 @@ impl InvalidPayloadRig {
let attributes: JsonPayloadAttributesV1 =
serde_json::from_value(payload_param_json.clone()).unwrap();
(fork_choice_state.into(), attributes.into())
(
fork_choice_state.into(),
JsonPayloadAttributes::V1(attributes).into(),
)
}
fn previous_payload_attributes(&self) -> PayloadAttributes {
@@ -693,6 +696,7 @@ async fn invalidates_all_descendants() {
fork_block.canonical_root(),
Arc::new(fork_block),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.unwrap();
@@ -789,6 +793,7 @@ async fn switches_heads() {
fork_block.canonical_root(),
Arc::new(fork_block),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.unwrap();
@@ -982,20 +987,22 @@ async fn payload_preparation() {
.await
.unwrap();
let payload_attributes = PayloadAttributes {
timestamp: rig
.harness
let payload_attributes = PayloadAttributes::new(
rig.harness
.chain
.slot_clock
.start_of(next_slot)
.unwrap()
.as_secs(),
prev_randao: *head
*head
.beacon_state
.get_randao_mix(head.beacon_state.current_epoch())
.unwrap(),
suggested_fee_recipient: fee_recipient,
};
fee_recipient,
None,
)
.downgrade_to_v1()
.unwrap();
assert_eq!(rig.previous_payload_attributes(), payload_attributes);
}
@@ -1035,7 +1042,7 @@ async fn invalid_parent() {
// Ensure the block built atop an invalid payload is invalid for import.
assert!(matches!(
rig.harness.chain.process_block(block.canonical_root(), block.clone(), CountUnrealized::True).await,
rig.harness.chain.process_block(block.canonical_root(), block.clone(), CountUnrealized::True, NotifyExecutionLayer::Yes).await,
Err(BlockError::ParentExecutionPayloadInvalid { parent_root: invalid_root })
if invalid_root == parent_root
));
@@ -1125,7 +1132,7 @@ async fn payload_preparation_before_transition_block() {
let (fork_choice_state, payload_attributes) = rig.previous_forkchoice_update_params();
let latest_block_hash = rig.latest_execution_block_hash();
assert_eq!(payload_attributes.suggested_fee_recipient, fee_recipient);
assert_eq!(payload_attributes.suggested_fee_recipient(), fee_recipient);
assert_eq!(fork_choice_state.head_block_hash, latest_block_hash);
}
@@ -1317,7 +1324,12 @@ async fn build_optimistic_chain(
for block in blocks {
rig.harness
.chain
.process_block(block.canonical_root(), block, CountUnrealized::True)
.process_block(
block.canonical_root(),
block,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.unwrap();
}
@@ -1367,18 +1379,16 @@ async fn build_optimistic_chain(
.body()
.execution_payload()
.unwrap()
.execution_payload
== <_>::default(),
.is_default_with_empty_roots(),
"the block *has not* undergone the merge transition"
);
assert!(
post_transition_block
!post_transition_block
.message()
.body()
.execution_payload()
.unwrap()
.execution_payload
!= <_>::default(),
.is_default_with_empty_roots(),
"the block *has* undergone the merge transition"
);
@@ -1879,6 +1889,7 @@ async fn recover_from_invalid_head_by_importing_blocks() {
fork_block.canonical_root(),
fork_block.clone(),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.unwrap();
@@ -7,8 +7,8 @@ use beacon_chain::test_utils::{
};
use beacon_chain::{
historical_blocks::HistoricalBlockError, migrate::MigratorConfig, BeaconChain,
BeaconChainError, BeaconChainTypes, BeaconSnapshot, ChainConfig, ServerSentEventHandler,
WhenSlotSkipped,
BeaconChainError, BeaconChainTypes, BeaconSnapshot, ChainConfig, NotifyExecutionLayer,
ServerSentEventHandler, WhenSlotSkipped,
};
use fork_choice::CountUnrealized;
use lazy_static::lazy_static;
@@ -2148,6 +2148,7 @@ async fn weak_subjectivity_sync() {
full_block.canonical_root(),
Arc::new(full_block),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.unwrap();
+3 -2
View File
@@ -6,7 +6,7 @@ use beacon_chain::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
OP_POOL_DB_KEY,
},
BeaconChain, StateSkipConfig, WhenSlotSkipped,
BeaconChain, NotifyExecutionLayer, StateSkipConfig, WhenSlotSkipped,
};
use fork_choice::CountUnrealized;
use lazy_static::lazy_static;
@@ -687,7 +687,8 @@ async fn run_skip_slot_test(skip_slots: u64) {
.process_block(
harness_a.chain.head_snapshot().beacon_block_root,
harness_a.chain.head_snapshot().beacon_block.clone(),
CountUnrealized::True
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
.unwrap(),
+6 -5
View File
@@ -40,9 +40,6 @@ use types::{
/// Interval between polling the eth1 node for genesis information.
pub const ETH1_GENESIS_UPDATE_INTERVAL_MILLIS: u64 = 7_000;
/// Timeout for checkpoint sync HTTP requests.
pub const CHECKPOINT_SYNC_HTTP_TIMEOUT: Duration = Duration::from_secs(60);
/// Builds a `Client` instance.
///
/// ## Notes
@@ -279,8 +276,12 @@ where
"remote_url" => %url,
);
let remote =
BeaconNodeHttpClient::new(url, Timeouts::set_all(CHECKPOINT_SYNC_HTTP_TIMEOUT));
let remote = BeaconNodeHttpClient::new(
url,
Timeouts::set_all(Duration::from_secs(
config.chain.checkpoint_sync_url_timeout,
)),
);
let slots_per_epoch = TEthSpec::slots_per_epoch();
let deposit_snapshot = if config.sync_eth1_chain {
+12 -1
View File
@@ -42,7 +42,7 @@ pub enum ClientGenesis {
/// The core configuration of a Lighthouse beacon node.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub data_dir: PathBuf,
data_dir: PathBuf,
/// Name of the directory inside the data directory where the main "hot" DB is located.
pub db_name: String,
/// Path where the freezer database will be located.
@@ -105,6 +105,17 @@ impl Default for Config {
}
impl Config {
/// Updates the data directory for the Client.
pub fn set_data_dir(&mut self, data_dir: PathBuf) {
self.data_dir = data_dir.clone();
self.http_api.data_dir = data_dir;
}
/// Gets the config's data_dir.
pub fn data_dir(&self) -> &PathBuf {
&self.data_dir
}
/// Get the database path without initialising it.
pub fn get_db_path(&self) -> PathBuf {
self.get_data_dir().join(&self.db_name)
+6 -4
View File
@@ -751,10 +751,11 @@ impl Service {
let deposit_count_to_finalize = eth1data_to_finalize.deposit_count;
if deposit_count_to_finalize > already_finalized {
match self.finalize_deposits(eth1data_to_finalize) {
Err(e) => error!(
Err(e) => warn!(
self.log,
"Failed to finalize deposit cache";
"error" => ?e,
"info" => "this should resolve on its own"
),
Ok(()) => info!(
self.log,
@@ -814,9 +815,10 @@ impl Service {
.block_by_hash(&eth1_data.block_hash)
.cloned()
.ok_or_else(|| {
Error::FailedToFinalizeDeposit(
"Finalized block not found in block cache".to_string(),
)
Error::FailedToFinalizeDeposit(format!(
"Finalized block not found in block cache: {:?}",
eth1_data.block_hash
))
})?;
self.inner
.deposit_cache
+19 -8
View File
@@ -7,11 +7,9 @@ use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use strum::IntoStaticStr;
use superstruct::superstruct;
#[cfg(feature = "withdrawals")]
use types::Withdrawal;
pub use types::{
Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader, FixedVector,
ForkName, Hash256, Uint256, VariableList,
ForkName, Hash256, Uint256, VariableList, Withdrawal,
};
pub mod auth;
@@ -245,11 +243,11 @@ impl<T: EthSpec> From<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T>
#[superstruct(
variants(V1, V2),
variant_attributes(derive(Clone, Debug, PartialEq),),
variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
)]
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct PayloadAttributes {
#[superstruct(getter(copy))]
pub timestamp: u64,
@@ -257,20 +255,33 @@ pub struct PayloadAttributes {
pub prev_randao: Hash256,
#[superstruct(getter(copy))]
pub suggested_fee_recipient: Address,
#[cfg(feature = "withdrawals")]
#[superstruct(only(V2))]
pub withdrawals: Option<Vec<Withdrawal>>,
}
impl PayloadAttributes {
pub fn new(
timestamp: u64,
prev_randao: Hash256,
suggested_fee_recipient: Address,
withdrawals: Option<Vec<Withdrawal>>,
) -> Self {
// this should always return the highest version
PayloadAttributes::V2(PayloadAttributesV2 {
timestamp,
prev_randao,
suggested_fee_recipient,
withdrawals,
})
}
pub fn downgrade_to_v1(self) -> Result<Self, Error> {
match self {
PayloadAttributes::V1(_) => Ok(self),
PayloadAttributes::V2(v2) => {
#[cfg(features = "withdrawals")]
if v2.withdrawals.is_some() {
return Err(Error::BadConversion(
"Downgrading from PayloadAttributesV2 with non-null withdrawaals"
"Downgrading from PayloadAttributesV2 with non-null withdrawals"
.to_string(),
));
}
@@ -375,8 +375,9 @@ pub struct JsonPayloadAttributes {
pub timestamp: u64,
pub prev_randao: Hash256,
pub suggested_fee_recipient: Address,
#[cfg(feature = "withdrawals")]
#[superstruct(only(V2))]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub withdrawals: Option<Vec<JsonWithdrawal>>,
}
@@ -392,7 +393,6 @@ impl From<PayloadAttributes> for JsonPayloadAttributes {
timestamp: pa.timestamp,
prev_randao: pa.prev_randao,
suggested_fee_recipient: pa.suggested_fee_recipient,
#[cfg(feature = "withdrawals")]
withdrawals: pa
.withdrawals
.map(|w| w.into_iter().map(Into::into).collect()),
@@ -413,7 +413,6 @@ impl From<JsonPayloadAttributes> for PayloadAttributes {
timestamp: jpa.timestamp,
prev_randao: jpa.prev_randao,
suggested_fee_recipient: jpa.suggested_fee_recipient,
#[cfg(feature = "withdrawals")]
withdrawals: jpa
.withdrawals
.map(|jw| jw.into_iter().map(Into::into).collect()),
+10 -21
View File
@@ -16,6 +16,7 @@ use types::{Address, ExecutionBlockHash, Hash256};
/// The number of payload IDs that will be stored for each `Engine`.
///
/// Since the size of each value is small (~100 bytes) a large number is used for safety.
/// FIXME: check this assumption now that the key includes entire payload attributes which now includes withdrawals
const PAYLOAD_ID_LRU_CACHE_SIZE: usize = 512;
/// Stores the remembered state of a engine.
@@ -97,9 +98,7 @@ pub struct ForkchoiceState {
#[derive(Hash, PartialEq, std::cmp::Eq)]
struct PayloadIdCacheKey {
pub head_block_hash: ExecutionBlockHash,
pub timestamp: u64,
pub prev_randao: Hash256,
pub suggested_fee_recipient: Address,
pub payload_attributes: PayloadAttributes,
}
#[derive(Debug)]
@@ -142,20 +141,13 @@ impl Engine {
pub async fn get_payload_id(
&self,
head_block_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
suggested_fee_recipient: Address,
head_block_hash: &ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
) -> Option<PayloadId> {
self.payload_id_cache
.lock()
.await
.get(&PayloadIdCacheKey {
head_block_hash,
timestamp,
prev_randao,
suggested_fee_recipient,
})
.get(&PayloadIdCacheKey::new(head_block_hash, payload_attributes))
.cloned()
}
@@ -171,8 +163,8 @@ impl Engine {
.await?;
if let Some(payload_id) = response.payload_id {
if let Some(key) =
payload_attributes.map(|pa| PayloadIdCacheKey::new(&forkchoice_state, &pa))
if let Some(key) = payload_attributes
.map(|pa| PayloadIdCacheKey::new(&forkchoice_state.head_block_hash, &pa))
{
self.payload_id_cache.lock().await.put(key, payload_id);
} else {
@@ -347,14 +339,11 @@ impl Engine {
}
}
// TODO: revisit this - do we need to key on withdrawals as well here?
impl PayloadIdCacheKey {
fn new(state: &ForkchoiceState, attributes: &PayloadAttributes) -> Self {
fn new(head_block_hash: &ExecutionBlockHash, attributes: &PayloadAttributes) -> Self {
Self {
head_block_hash: state.head_block_hash,
timestamp: attributes.timestamp(),
prev_randao: attributes.prev_randao(),
suggested_fee_recipient: attributes.suggested_fee_recipient(),
head_block_hash: head_block_hash.clone(),
payload_attributes: attributes.clone(),
}
}
}
+461 -182
View File
@@ -12,6 +12,7 @@ pub use engine_api::*;
pub use engine_api::{http, http::deposit_methods, http::HttpJsonRpc};
use engines::{Engine, EngineError};
pub use engines::{EngineState, ForkchoiceState};
use eth2::types::{builder_bid::SignedBuilderBid, ForkVersionedResponse};
use fork_choice::ForkchoiceUpdateParameters;
use lru::LruCache;
use payload_status::process_payload_status;
@@ -21,11 +22,13 @@ use serde::{Deserialize, Serialize};
use slog::{crit, debug, error, info, trace, warn, Logger};
use slot_clock::SlotClock;
use std::collections::HashMap;
use std::fmt;
use std::future::Future;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use strum::AsRefStr;
use task_executor::TaskExecutor;
use tokio::{
sync::{Mutex, MutexGuard, RwLock},
@@ -34,12 +37,14 @@ use tokio::{
use tokio_stream::wrappers::WatchStream;
#[cfg(feature = "withdrawals")]
use types::Withdrawal;
use types::{AbstractExecPayload, Blob, ExecPayload, ExecutionPayloadEip4844, KzgCommitment};
use types::{AbstractExecPayload, Blob, ExecPayload, KzgCommitment};
use types::{
BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ForkName,
ProposerPreparationData, PublicKeyBytes, SignedBeaconBlock, Slot,
ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock, Slot, Uint256,
};
use types::{
ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadEip4844, ExecutionPayloadMerge,
};
use types::{ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge};
mod engine_api;
mod engines;
@@ -70,6 +75,14 @@ const DEFAULT_SUGGESTED_FEE_RECIPIENT: [u8; 20] =
const CONFIG_POLL_INTERVAL: Duration = Duration::from_secs(60);
/// A payload alongside some information about where it came from.
enum ProvenancedPayload<P> {
/// A good ol' fashioned farm-to-table payload from your local EE.
Local(P),
/// A payload from a builder (e.g. mev-boost).
Builder(P),
}
#[derive(Debug)]
pub enum Error {
NoEngine,
@@ -77,6 +90,7 @@ pub enum Error {
ApiError(ApiError),
Builder(builder_client::Error),
NoHeaderFromBuilder,
CannotProduceHeader,
EngineError(Box<EngineError>),
NotSynced,
ShuttingDown,
@@ -602,22 +616,16 @@ impl<T: EthSpec> ExecutionLayer<T> {
///
/// The result will be returned from the first node that returns successfully. No more nodes
/// will be contacted.
#[allow(clippy::too_many_arguments)]
pub async fn get_payload<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
proposer_index: u64,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
builder_params: BuilderParams,
current_fork: ForkName,
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
spec: &ChainSpec,
) -> Result<BlockProposalContents<T, Payload>, Error> {
let suggested_fee_recipient = self.get_suggested_fee_recipient(proposer_index).await;
match Payload::block_type() {
let payload_result = match Payload::block_type() {
BlockType::Blinded => {
let _timer = metrics::start_timer_vec(
&metrics::EXECUTION_LAYER_REQUEST_TIMES,
@@ -625,14 +633,10 @@ impl<T: EthSpec> ExecutionLayer<T> {
);
self.get_blinded_payload(
parent_hash,
timestamp,
prev_randao,
suggested_fee_recipient,
payload_attributes,
forkchoice_update_params,
builder_params,
current_fork,
#[cfg(feature = "withdrawals")]
withdrawals,
spec,
)
.await
@@ -644,32 +648,58 @@ impl<T: EthSpec> ExecutionLayer<T> {
);
self.get_full_payload(
parent_hash,
timestamp,
prev_randao,
suggested_fee_recipient,
payload_attributes,
forkchoice_update_params,
current_fork,
#[cfg(feature = "withdrawals")]
withdrawals,
)
.await
.map(ProvenancedPayload::Local)
}
};
// Track some metrics and return the result.
match payload_result {
Ok(ProvenancedPayload::Local(block_proposal_contents)) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
&[metrics::SUCCESS],
);
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_SOURCE,
&[metrics::LOCAL],
);
Ok(block_proposal_contents)
}
Ok(ProvenancedPayload::Builder(block_proposal_contents)) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
&[metrics::SUCCESS],
);
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_SOURCE,
&[metrics::BUILDER],
);
Ok(block_proposal_contents)
}
Err(e) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
&[metrics::FAILURE],
);
Err(e)
}
}
}
#[allow(clippy::too_many_arguments)]
async fn get_blinded_payload<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
suggested_fee_recipient: Address,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
builder_params: BuilderParams,
current_fork: ForkName,
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
spec: &ChainSpec,
) -> Result<BlockProposalContents<T, Payload>, Error> {
) -> Result<ProvenancedPayload<BlockProposalContents<T, Payload>>, Error> {
if let Some(builder) = self.builder() {
let slot = builder_params.slot;
let pubkey = builder_params.pubkey;
@@ -683,173 +713,238 @@ impl<T: EthSpec> ExecutionLayer<T> {
"pubkey" => ?pubkey,
"parent_hash" => ?parent_hash,
);
let (relay_result, local_result) = tokio::join!(
builder.get_builder_header::<T, Payload>(slot, parent_hash, &pubkey),
self.get_full_payload_caching(
parent_hash,
timestamp,
prev_randao,
suggested_fee_recipient,
forkchoice_update_params,
current_fork,
#[cfg(feature = "withdrawals")]
withdrawals,
)
// Wait for the builder *and* local EL to produce a payload (or return an error).
let ((relay_result, relay_duration), (local_result, local_duration)) = tokio::join!(
timed_future(metrics::GET_BLINDED_PAYLOAD_BUILDER, async {
builder
.get_builder_header::<T, Payload>(slot, parent_hash, &pubkey)
.await
}),
timed_future(metrics::GET_BLINDED_PAYLOAD_LOCAL, async {
self.get_full_payload_caching::<Payload>(
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
)
.await
})
);
info!(
self.log(),
"Requested blinded execution payload";
"relay_fee_recipient" => match &relay_result {
Ok(Some(r)) => format!("{:?}", r.data.message.header.fee_recipient()),
Ok(None) => "empty response".to_string(),
Err(_) => "request failed".to_string(),
},
"relay_response_ms" => relay_duration.as_millis(),
"local_fee_recipient" => match &local_result {
Ok(proposal_contents) => format!("{:?}", proposal_contents.payload().fee_recipient()),
Err(_) => "request failed".to_string()
},
"local_response_ms" => local_duration.as_millis(),
"parent_hash" => ?parent_hash,
);
return match (relay_result, local_result) {
(Err(e), Ok(local)) => {
warn!(
self.log(),
"Unable to retrieve a payload from a connected \
builder, falling back to the local execution client: {e:?}"
"Builder error when requesting payload";
"info" => "falling back to local execution client",
"relay_error" => ?e,
"local_block_hash" => ?local.payload().block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(local)
Ok(ProvenancedPayload::Local(local))
}
(Ok(None), Ok(local)) => {
info!(
self.log(),
"No payload provided by connected builder. \
Attempting to propose through local execution engine"
"Builder did not return a payload";
"info" => "falling back to local execution client",
"local_block_hash" => ?local.payload().block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(local)
Ok(ProvenancedPayload::Local(local))
}
(Ok(Some(relay)), Ok(local)) => {
let local_payload = local.payload();
let is_signature_valid = relay.data.verify_signature(spec);
let header = relay.data.message.header;
let header = &relay.data.message.header;
info!(
self.log(),
"Received a payload header from the connected builder";
"block_hash" => ?header.block_hash(),
"Received local and builder payloads";
"relay_block_hash" => ?header.block_hash(),
"local_block_hash" => ?local.payload().block_hash(),
"parent_hash" => ?parent_hash,
);
let relay_value = relay.data.message.value;
let configured_value = self.inner.builder_profit_threshold;
if relay_value < configured_value {
info!(
self.log(),
"The value offered by the connected builder does not meet \
the configured profit threshold. Using local payload.";
"configured_value" => ?configured_value, "relay_value" => ?relay_value
);
Ok(local)
} else if header.parent_hash() != parent_hash {
warn!(
self.log(),
"Invalid parent hash from connected builder, \
falling back to local execution engine."
);
Ok(local)
} else if header.prev_randao() != prev_randao {
warn!(
self.log(),
"Invalid prev randao from connected builder, \
falling back to local execution engine."
);
Ok(local)
} else if header.timestamp() != local_payload.timestamp() {
warn!(
self.log(),
"Invalid timestamp from connected builder, \
falling back to local execution engine."
);
Ok(local)
} else if header.block_number() != local_payload.block_number() {
warn!(
self.log(),
"Invalid block number from connected builder, \
falling back to local execution engine."
);
Ok(local)
} else if !matches!(relay.version, Some(ForkName::Merge)) {
// Once fork information is added to the payload, we will need to
// check that the local and relay payloads match. At this point, if
// we are requesting a payload at all, we have to assume this is
// the Bellatrix fork.
warn!(
self.log(),
"Invalid fork from connected builder, falling \
back to local execution engine."
);
Ok(local)
} else if !is_signature_valid {
let pubkey_bytes = relay.data.message.pubkey;
warn!(self.log(), "Invalid signature for pubkey {pubkey_bytes} on \
bid from connected builder, falling back to local execution engine.");
Ok(local)
} else {
if header.fee_recipient() != suggested_fee_recipient {
match verify_builder_bid(
&relay,
parent_hash,
payload_attributes.prev_randao(),
payload_attributes.timestamp(),
Some(local.payload().block_number()),
self.inner.builder_profit_threshold,
spec,
) {
Ok(()) => Ok(ProvenancedPayload::Builder(
//FIXME(sean) the builder API needs to be updated
// NOTE the comment above was removed in the
// rebase with unstable.. I think it goes
// here now?
BlockProposalContents::Payload(relay.data.message.header),
)),
Err(reason) if !reason.payload_invalid() => {
info!(
self.log(),
"Fee recipient from connected builder does \
not match, using it anyways."
"Builder payload ignored";
"info" => "using local payload",
"reason" => %reason,
"relay_block_hash" => ?header.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local))
}
Err(reason) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS,
&[reason.as_ref().as_ref()],
);
warn!(
self.log(),
"Builder returned invalid payload";
"info" => "using local payload",
"reason" => %reason,
"relay_block_hash" => ?header.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local))
}
//FIXME(sean) the builder API needs to be updated
Ok(BlockProposalContents::Payload(header))
}
}
(relay_result, Err(local_error)) => {
warn!(self.log(), "Failure from local execution engine. Attempting to \
propose through connected builder"; "error" => ?local_error);
relay_result
.map_err(Error::Builder)?
.ok_or(Error::NoHeaderFromBuilder)
.map(|d| {
(Ok(Some(relay)), Err(local_error)) => {
let header = &relay.data.message.header;
info!(
self.log(),
"Received builder payload with local error";
"relay_block_hash" => ?header.block_hash(),
"local_error" => ?local_error,
"parent_hash" => ?parent_hash,
);
match verify_builder_bid(
&relay,
parent_hash,
payload_attributes.prev_randao(),
payload_attributes.timestamp(),
None,
self.inner.builder_profit_threshold,
spec,
) {
Ok(()) => Ok(ProvenancedPayload::Builder(
//FIXME(sean) the builder API needs to be updated
BlockProposalContents::Payload(d.data.message.header)
})
// NOTE the comment above was removed in the
// rebase with unstable.. I think it goes
// here now?
BlockProposalContents::Payload(relay.data.message.header),
)),
// If the payload is valid then use it. The local EE failed
// to produce a payload so we have no alternative.
Err(e) if !e.payload_invalid() => Ok(ProvenancedPayload::Builder(
//FIXME(sean) the builder API needs to be updated
// NOTE the comment above was removed in the
// rebase with unstable.. I think it goes
// here now?
BlockProposalContents::Payload(relay.data.message.header),
)),
Err(reason) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS,
&[reason.as_ref().as_ref()],
);
crit!(
self.log(),
"Builder returned invalid payload";
"info" => "no local payload either - unable to propose block",
"reason" => %reason,
"relay_block_hash" => ?header.block_hash(),
"parent_hash" => ?parent_hash,
);
Err(Error::CannotProduceHeader)
}
}
}
(Err(relay_error), Err(local_error)) => {
crit!(
self.log(),
"Unable to produce execution payload";
"info" => "the local EL and builder both failed - unable to propose block",
"relay_error" => ?relay_error,
"local_error" => ?local_error,
"parent_hash" => ?parent_hash,
);
Err(Error::CannotProduceHeader)
}
(Ok(None), Err(local_error)) => {
crit!(
self.log(),
"Unable to produce execution payload";
"info" => "the local EL failed and the builder returned nothing - \
the block proposal will be missed",
"local_error" => ?local_error,
"parent_hash" => ?parent_hash,
);
Err(Error::CannotProduceHeader)
}
};
}
ChainHealth::Unhealthy(condition) => {
info!(self.log(), "Due to poor chain health the local execution engine will be used \
for payload construction. To adjust chain health conditions \
Use `builder-fallback` prefixed flags";
"failed_condition" => ?condition)
}
ChainHealth::Unhealthy(condition) => info!(
self.log(),
"Chain is unhealthy, using local payload";
"info" => "this helps protect the network. the --builder-fallback flags \
can adjust the expected health conditions.",
"failed_condition" => ?condition
),
// Intentional no-op, so we never attempt builder API proposals pre-merge.
ChainHealth::PreMerge => (),
ChainHealth::Optimistic => info!(self.log(), "The local execution engine is syncing \
so the builder network cannot safely be used. Attempting \
to build a block with the local execution engine"),
ChainHealth::Optimistic => info!(
self.log(),
"Chain is optimistic; can't build payload";
"info" => "the local execution engine is syncing and the builder network \
cannot safely be used - unable to propose block"
),
}
}
self.get_full_payload_caching(
parent_hash,
timestamp,
prev_randao,
suggested_fee_recipient,
payload_attributes,
forkchoice_update_params,
current_fork,
#[cfg(feature = "withdrawals")]
withdrawals,
)
.await
.map(ProvenancedPayload::Local)
}
/// Get a full payload without caching its result in the execution layer's payload cache.
async fn get_full_payload<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
suggested_fee_recipient: Address,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
) -> Result<BlockProposalContents<T, Payload>, Error> {
self.get_full_payload_with(
parent_hash,
timestamp,
prev_randao,
suggested_fee_recipient,
payload_attributes,
forkchoice_update_params,
current_fork,
#[cfg(feature = "withdrawals")]
withdrawals,
noop,
)
.await
@@ -859,22 +954,15 @@ impl<T: EthSpec> ExecutionLayer<T> {
async fn get_full_payload_caching<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
suggested_fee_recipient: Address,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
) -> Result<BlockProposalContents<T, Payload>, Error> {
self.get_full_payload_with(
parent_hash,
timestamp,
prev_randao,
suggested_fee_recipient,
payload_attributes,
forkchoice_update_params,
current_fork,
#[cfg(feature = "withdrawals")]
withdrawals,
Self::cache_payload,
)
.await
@@ -883,20 +971,15 @@ impl<T: EthSpec> ExecutionLayer<T> {
async fn get_full_payload_with<Payload: AbstractExecPayload<T>>(
&self,
parent_hash: ExecutionBlockHash,
timestamp: u64,
prev_randao: Hash256,
suggested_fee_recipient: Address,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
f: fn(&ExecutionLayer<T>, &ExecutionPayload<T>) -> Option<ExecutionPayload<T>>,
) -> Result<BlockProposalContents<T, Payload>, Error> {
#[cfg(feature = "withdrawals")]
let withdrawals_ref = &withdrawals;
self.engine()
.request(move |engine| async move {
let payload_id = if let Some(id) = engine
.get_payload_id(parent_hash, timestamp, prev_randao, suggested_fee_recipient)
.get_payload_id(&parent_hash, payload_attributes)
.await
{
// The payload id has been cached for this engine.
@@ -921,20 +1004,11 @@ impl<T: EthSpec> ExecutionLayer<T> {
.finalized_hash
.unwrap_or_else(ExecutionBlockHash::zero),
};
// This must always be the latest PayloadAttributes
// FIXME: How to non-capella EIP4844 testnets handle this?
let payload_attributes = PayloadAttributes::V2(PayloadAttributesV2 {
timestamp,
prev_randao,
suggested_fee_recipient,
#[cfg(feature = "withdrawals")]
withdrawals: withdrawals_ref.clone(),
});
let response = engine
.notify_forkchoice_updated(
fork_choice_state,
Some(payload_attributes),
Some(payload_attributes.clone()),
self.log(),
)
.await?;
@@ -963,9 +1037,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
debug!(
self.log(),
"Issuing engine_getBlobsBundle";
"suggested_fee_recipient" => ?suggested_fee_recipient,
"prev_randao" => ?prev_randao,
"timestamp" => timestamp,
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
"prev_randao" => ?payload_attributes.prev_randao(),
"timestamp" => payload_attributes.timestamp(),
"parent_hash" => ?parent_hash,
);
Some(engine.api.get_blobs_bundle_v1::<T>(payload_id).await)
@@ -976,16 +1050,16 @@ impl<T: EthSpec> ExecutionLayer<T> {
debug!(
self.log(),
"Issuing engine_getPayload";
"suggested_fee_recipient" => ?suggested_fee_recipient,
"prev_randao" => ?prev_randao,
"timestamp" => timestamp,
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
"prev_randao" => ?payload_attributes.prev_randao(),
"timestamp" => payload_attributes.timestamp(),
"parent_hash" => ?parent_hash,
);
engine.api.get_payload::<T>(current_fork, payload_id).await
};
let (blob, payload) = tokio::join!(blob_fut, payload_fut);
let payload = payload.map(|full_payload| {
if full_payload.fee_recipient() != suggested_fee_recipient {
if full_payload.fee_recipient() != payload_attributes.suggested_fee_recipient() {
error!(
self.log(),
"Inconsistent fee recipient";
@@ -995,7 +1069,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
ensure that the value of suggested_fee_recipient is set correctly and \
that the Execution Engine is trusted.",
"fee_recipient" => ?full_payload.fee_recipient(),
"suggested_fee_recipient" => ?suggested_fee_recipient,
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
);
}
if f(self, &full_payload).is_some() {
@@ -1597,18 +1671,223 @@ impl<T: EthSpec> ExecutionLayer<T> {
"Sending block to builder";
"root" => ?block_root,
);
if let Some(builder) = self.builder() {
builder
.post_builder_blinded_blocks(block)
.await
.map_err(Error::Builder)
.map(|d| d.data)
let (payload_result, duration) =
timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async {
builder
.post_builder_blinded_blocks(block)
.await
.map_err(Error::Builder)
.map(|d| d.data)
})
.await;
match &payload_result {
Ok(payload) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME,
&[metrics::SUCCESS],
);
info!(
self.log(),
"Builder successfully revealed payload";
"relay_response_ms" => duration.as_millis(),
"block_root" => ?block_root,
"fee_recipient" => ?payload.fee_recipient(),
"block_hash" => ?payload.block_hash(),
"parent_hash" => ?payload.parent_hash()
)
}
Err(e) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME,
&[metrics::FAILURE],
);
crit!(
self.log(),
"Builder failed to reveal payload";
"info" => "this relay failure may cause a missed proposal",
"error" => ?e,
"relay_response_ms" => duration.as_millis(),
"block_root" => ?block_root,
"parent_hash" => ?block
.message()
.execution_payload()
.map(|payload| format!("{}", payload.parent_hash()))
.unwrap_or_else(|_| "unknown".to_string())
)
}
}
payload_result
} else {
Err(Error::NoPayloadBuilder)
}
}
}
#[derive(AsRefStr)]
#[strum(serialize_all = "snake_case")]
enum InvalidBuilderPayload {
LowValue {
profit_threshold: Uint256,
payload_value: Uint256,
},
ParentHash {
payload: ExecutionBlockHash,
expected: ExecutionBlockHash,
},
PrevRandao {
payload: Hash256,
expected: Hash256,
},
Timestamp {
payload: u64,
expected: u64,
},
BlockNumber {
payload: u64,
expected: Option<u64>,
},
Fork {
payload: Option<ForkName>,
expected: ForkName,
},
Signature {
signature: Signature,
pubkey: PublicKeyBytes,
},
}
impl InvalidBuilderPayload {
/// Returns `true` if a payload is objectively invalid and should never be included on chain.
fn payload_invalid(&self) -> bool {
match self {
// A low-value payload isn't invalid, it should just be avoided if possible.
InvalidBuilderPayload::LowValue { .. } => false,
InvalidBuilderPayload::ParentHash { .. } => true,
InvalidBuilderPayload::PrevRandao { .. } => true,
InvalidBuilderPayload::Timestamp { .. } => true,
InvalidBuilderPayload::BlockNumber { .. } => true,
InvalidBuilderPayload::Fork { .. } => true,
InvalidBuilderPayload::Signature { .. } => true,
}
}
}
impl fmt::Display for InvalidBuilderPayload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InvalidBuilderPayload::LowValue {
profit_threshold,
payload_value,
} => write!(
f,
"payload value of {} does not meet user-configured profit-threshold of {}",
payload_value, profit_threshold
),
InvalidBuilderPayload::ParentHash { payload, expected } => {
write!(f, "payload block hash was {} not {}", payload, expected)
}
InvalidBuilderPayload::PrevRandao { payload, expected } => {
write!(f, "payload prev randao was {} not {}", payload, expected)
}
InvalidBuilderPayload::Timestamp { payload, expected } => {
write!(f, "payload timestamp was {} not {}", payload, expected)
}
InvalidBuilderPayload::BlockNumber { payload, expected } => {
write!(f, "payload block number was {} not {:?}", payload, expected)
}
InvalidBuilderPayload::Fork { payload, expected } => {
write!(f, "payload fork was {:?} not {}", payload, expected)
}
InvalidBuilderPayload::Signature { signature, pubkey } => write!(
f,
"invalid payload signature {} for pubkey {}",
signature, pubkey
),
}
}
}
/// Perform some cursory, non-exhaustive validation of the bid returned from the builder.
fn verify_builder_bid<T: EthSpec, Payload: AbstractExecPayload<T>>(
bid: &ForkVersionedResponse<SignedBuilderBid<T, Payload>>,
parent_hash: ExecutionBlockHash,
prev_randao: Hash256,
timestamp: u64,
block_number: Option<u64>,
profit_threshold: Uint256,
spec: &ChainSpec,
) -> Result<(), Box<InvalidBuilderPayload>> {
let is_signature_valid = bid.data.verify_signature(spec);
let header = &bid.data.message.header;
let payload_value = bid.data.message.value;
// Avoid logging values that we can't represent with our Prometheus library.
let payload_value_gwei = bid.data.message.value / 1_000_000_000;
if payload_value_gwei <= Uint256::from(i64::max_value()) {
metrics::set_gauge_vec(
&metrics::EXECUTION_LAYER_PAYLOAD_BIDS,
&[metrics::BUILDER],
payload_value_gwei.low_u64() as i64,
);
}
if payload_value < profit_threshold {
Err(Box::new(InvalidBuilderPayload::LowValue {
profit_threshold,
payload_value,
}))
} else if header.parent_hash() != parent_hash {
Err(Box::new(InvalidBuilderPayload::ParentHash {
payload: header.parent_hash(),
expected: parent_hash,
}))
} else if header.prev_randao() != prev_randao {
Err(Box::new(InvalidBuilderPayload::PrevRandao {
payload: header.prev_randao(),
expected: prev_randao,
}))
} else if header.timestamp() != timestamp {
Err(Box::new(InvalidBuilderPayload::Timestamp {
payload: header.timestamp(),
expected: timestamp,
}))
} else if block_number.map_or(false, |n| n != header.block_number()) {
Err(Box::new(InvalidBuilderPayload::BlockNumber {
payload: header.block_number(),
expected: block_number,
}))
} else if !matches!(bid.version, Some(ForkName::Merge)) {
// Once fork information is added to the payload, we will need to
// check that the local and relay payloads match. At this point, if
// we are requesting a payload at all, we have to assume this is
// the Bellatrix fork.
Err(Box::new(InvalidBuilderPayload::Fork {
payload: bid.version,
expected: ForkName::Merge,
}))
} else if !is_signature_valid {
Err(Box::new(InvalidBuilderPayload::Signature {
signature: bid.data.signature.clone(),
pubkey: bid.data.message.pubkey,
}))
} else {
Ok(())
}
}
/// A helper function to record the time it takes to execute a future.
async fn timed_future<F: Future<Output = T>, T>(metric: &str, future: F) -> (T, Duration) {
let start = Instant::now();
let result = future.await;
let duration = start.elapsed();
metrics::observe_timer_vec(&metrics::EXECUTION_LAYER_REQUEST_TIMES, &[metric], duration);
(result, duration)
}
#[cfg(test)]
mod test {
use super::*;
+35 -1
View File
@@ -4,10 +4,17 @@ pub const HIT: &str = "hit";
pub const MISS: &str = "miss";
pub const GET_PAYLOAD: &str = "get_payload";
pub const GET_BLINDED_PAYLOAD: &str = "get_blinded_payload";
pub const GET_BLINDED_PAYLOAD_LOCAL: &str = "get_blinded_payload_local";
pub const GET_BLINDED_PAYLOAD_BUILDER: &str = "get_blinded_payload_builder";
pub const POST_BLINDED_PAYLOAD_BUILDER: &str = "post_blinded_payload_builder";
pub const NEW_PAYLOAD: &str = "new_payload";
pub const FORKCHOICE_UPDATED: &str = "forkchoice_updated";
pub const GET_TERMINAL_POW_BLOCK_HASH: &str = "get_terminal_pow_block_hash";
pub const IS_VALID_TERMINAL_POW_BLOCK_HASH: &str = "is_valid_terminal_pow_block_hash";
pub const LOCAL: &str = "local";
pub const BUILDER: &str = "builder";
pub const SUCCESS: &str = "success";
pub const FAILURE: &str = "failure";
lazy_static::lazy_static! {
pub static ref EXECUTION_LAYER_PROPOSER_INSERTED: Result<IntCounter> = try_create_int_counter(
@@ -18,9 +25,11 @@ lazy_static::lazy_static! {
"execution_layer_proposer_data_updated",
"Count of times new proposer data is supplied",
);
pub static ref EXECUTION_LAYER_REQUEST_TIMES: Result<HistogramVec> = try_create_histogram_vec(
pub static ref EXECUTION_LAYER_REQUEST_TIMES: Result<HistogramVec> =
try_create_histogram_vec_with_buckets(
"execution_layer_request_times",
"Duration of calls to ELs",
decimal_buckets(-2, 1),
&["method"]
);
pub static ref EXECUTION_LAYER_PAYLOAD_ATTRIBUTES_LOOKAHEAD: Result<Histogram> = try_create_histogram(
@@ -41,4 +50,29 @@ lazy_static::lazy_static! {
"Indicates the payload status returned for a particular method",
&["method", "status"]
);
pub static ref EXECUTION_LAYER_GET_PAYLOAD_OUTCOME: Result<IntCounterVec> = try_create_int_counter_vec(
"execution_layer_get_payload_outcome",
"The success/failure outcomes from calling get_payload",
&["outcome"]
);
pub static ref EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME: Result<IntCounterVec> = try_create_int_counter_vec(
"execution_layer_builder_reveal_payload_outcome",
"The success/failure outcomes from a builder un-blinding a payload",
&["outcome"]
);
pub static ref EXECUTION_LAYER_GET_PAYLOAD_SOURCE: Result<IntCounterVec> = try_create_int_counter_vec(
"execution_layer_get_payload_source",
"The source of each payload returned from get_payload",
&["source"]
);
pub static ref EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS: Result<IntCounterVec> = try_create_int_counter_vec(
"execution_layer_get_payload_builder_rejections",
"The reasons why a payload from a builder was rejected",
&["reason"]
);
pub static ref EXECUTION_LAYER_PAYLOAD_BIDS: Result<IntGaugeVec> = try_create_int_gauge_vec(
"execution_layer_payload_bids",
"The gwei bid value of payloads received by local EEs or builders. Only shows values up to i64::max_value.",
&["source"]
);
}
@@ -1,5 +1,5 @@
use crate::test_utils::DEFAULT_JWT_SECRET;
use crate::{Config, ExecutionLayer, PayloadAttributes, PayloadAttributesV1};
use crate::{Config, ExecutionLayer, PayloadAttributes};
use async_trait::async_trait;
use eth2::types::{BlockId, StateId, ValidatorId};
use eth2::{BeaconNodeHttpClient, Timeouts};
@@ -289,11 +289,8 @@ impl<E: EthSpec> mev_build_rs::BlindedBlockProvider for MockBuilder<E> {
.map_err(convert_err)?;
// FIXME: think about proper fork here
let payload_attributes = PayloadAttributes::V1(PayloadAttributesV1 {
timestamp,
prev_randao: *prev_randao,
suggested_fee_recipient: fee_recipient,
});
let payload_attributes =
PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, None);
self.el
.insert_proposer(slot, head_block_root, val_index, payload_attributes)
@@ -306,18 +303,17 @@ impl<E: EthSpec> mev_build_rs::BlindedBlockProvider for MockBuilder<E> {
finalized_hash: Some(finalized_execution_hash),
};
let payload_attributes =
PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, None);
let payload = self
.el
.get_full_payload_caching::<BlindedPayload<E>>(
head_execution_hash,
timestamp,
*prev_randao,
fee_recipient,
&payload_attributes,
forkchoice_update_params,
// TODO: do we need to write a test for this if this is Capella fork?
ForkName::Merge,
#[cfg(feature = "withdrawals")]
None,
)
.await
.map_err(convert_err)?
@@ -98,33 +98,16 @@ impl<T: EthSpec> MockExecutionLayer<T> {
justified_hash: None,
finalized_hash: None,
};
// FIXME: this is just best guess for how to deal with forks here..
let payload_attributes = match &latest_execution_block {
&Block::PoS(ref pos_block) => match pos_block {
&ExecutionPayload::Merge(_) => PayloadAttributes::V1(PayloadAttributesV1 {
timestamp,
prev_randao,
suggested_fee_recipient: Address::repeat_byte(42),
}),
&ExecutionPayload::Capella(_) | &ExecutionPayload::Eip4844(_) => {
PayloadAttributes::V2(PayloadAttributesV2 {
timestamp,
prev_randao,
suggested_fee_recipient: Address::repeat_byte(42),
// FIXME: think about adding withdrawals here..
#[cfg(feature = "withdrawals")]
withdrawals: Some(vec![]),
})
}
},
// I guess a PoW blocks means we should use Merge?
&Block::PoW(_) => PayloadAttributes::V1(PayloadAttributesV1 {
timestamp,
prev_randao,
suggested_fee_recipient: Address::repeat_byte(42),
}),
};
let payload_attributes = PayloadAttributes::new(
timestamp,
prev_randao,
Address::repeat_byte(42),
// FIXME: think about how to handle different forks / withdrawals here..
#[cfg(feature = "withdrawals")]
Some(vec![]),
#[cfg(not(feature = "withdrawals"))]
None,
);
// Insert a proposer to ensure the fork choice updated command works.
let slot = Slot::new(0);
@@ -150,19 +133,18 @@ impl<T: EthSpec> MockExecutionLayer<T> {
slot,
chain_health: ChainHealth::Healthy,
};
let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await;
let payload_attributes =
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None);
let payload: ExecutionPayload<T> = self
.el
.get_payload::<FullPayload<T>>(
parent_hash,
timestamp,
prev_randao,
validator_index,
&payload_attributes,
forkchoice_update_params,
builder_params,
// FIXME: do we need to consider other forks somehow? What about withdrawals?
ForkName::Merge,
#[cfg(feature = "withdrawals")]
Some(vec![]),
&self.spec,
)
.await
@@ -186,19 +168,18 @@ impl<T: EthSpec> MockExecutionLayer<T> {
slot,
chain_health: ChainHealth::Healthy,
};
let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await;
let payload_attributes =
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None);
let payload_header = self
.el
.get_payload::<BlindedPayload<T>>(
parent_hash,
timestamp,
prev_randao,
validator_index,
&payload_attributes,
forkchoice_update_params,
builder_params,
// FIXME: do we need to consider other forks somehow? What about withdrawals?
ForkName::Merge,
#[cfg(feature = "withdrawals")]
Some(vec![]),
&self.spec,
)
.await
+1 -1
View File
@@ -24,7 +24,7 @@ pub fn new_env() -> Environment<MinimalEthSpec> {
#[test]
fn basic() {
let mut env = new_env();
let env = new_env();
let log = env.core_context().log().clone();
let mut spec = env.eth2_config().spec.clone();
+3
View File
@@ -36,6 +36,9 @@ safe_arith = {path = "../../consensus/safe_arith"}
task_executor = { path = "../../common/task_executor" }
lru = "0.7.7"
tree_hash = "0.4.1"
sysinfo = "0.26.5"
system_health = { path = "../../common/system_health" }
directory = { path = "../../common/directory" }
[dev-dependencies]
store = { path = "../store" }
+150 -1
View File
@@ -26,12 +26,14 @@ use beacon_chain::{
BeaconChainTypes, ProduceBlockVerification, WhenSlotSkipped,
};
pub use block_id::BlockId;
use directory::DEFAULT_ROOT_DIR;
use eth2::types::{
self as api_types, EndpointVersion, SkipRandaoVerification, ValidatorId, ValidatorStatus,
};
use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage};
use lighthouse_version::version_with_platform;
use network::{NetworkMessage, NetworkSenders, ValidatorSubscriptionMessage};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use slog::{crit, debug, error, info, warn, Logger};
use slot_clock::SlotClock;
@@ -43,6 +45,8 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use sysinfo::{System, SystemExt};
use system_health::observe_system_health_bn;
use tokio::sync::mpsc::{Sender, UnboundedSender};
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
use types::{
@@ -110,6 +114,7 @@ pub struct Config {
pub tls_config: Option<TlsConfig>,
pub allow_sync_stalled: bool,
pub spec_fork_name: Option<ForkName>,
pub data_dir: PathBuf,
}
impl Default for Config {
@@ -122,6 +127,7 @@ impl Default for Config {
tls_config: None,
allow_sync_stalled: false,
spec_fork_name: None,
data_dir: PathBuf::from(DEFAULT_ROOT_DIR),
}
}
}
@@ -323,6 +329,10 @@ pub fn serve<T: BeaconChainTypes>(
}
});
// Create a `warp` filter for the data_dir.
let inner_data_dir = ctx.config.data_dir.clone();
let data_dir_filter = warp::any().map(move || inner_data_dir.clone());
// Create a `warp` filter that provides access to the beacon chain.
let inner_ctx = ctx.clone();
let chain_filter =
@@ -431,6 +441,37 @@ pub fn serve<T: BeaconChainTypes>(
let inner_ctx = ctx.clone();
let log_filter = warp::any().map(move || inner_ctx.log.clone());
// Create a `warp` filter that provides access to local system information.
let system_info = Arc::new(RwLock::new(sysinfo::System::new()));
{
// grab write access for initialisation
let mut system_info = system_info.write();
system_info.refresh_disks_list();
system_info.refresh_networks_list();
system_info.refresh_cpu_specifics(sysinfo::CpuRefreshKind::everything());
system_info.refresh_cpu();
} // end lock
let system_info_filter =
warp::any()
.map(move || system_info.clone())
.map(|sysinfo: Arc<RwLock<System>>| {
{
// refresh stats
let mut sysinfo_lock = sysinfo.write();
sysinfo_lock.refresh_memory();
sysinfo_lock.refresh_cpu_specifics(sysinfo::CpuRefreshKind::everything());
sysinfo_lock.refresh_cpu();
sysinfo_lock.refresh_system();
sysinfo_lock.refresh_networks();
sysinfo_lock.refresh_disks();
} // end lock
sysinfo
});
let app_start = std::time::Instant::now();
let app_start_filter = warp::any().map(move || app_start);
/*
*
* Start of HTTP method definitions.
@@ -891,6 +932,37 @@ pub fn serve<T: BeaconChainTypes>(
},
);
// GET beacon/states/{state_id}/randao?epoch
let get_beacon_state_randao = beacon_states_path
.clone()
.and(warp::path("randao"))
.and(warp::query::<api_types::RandaoQuery>())
.and(warp::path::end())
.and_then(
|state_id: StateId, chain: Arc<BeaconChain<T>>, query: api_types::RandaoQuery| {
blocking_json_task(move || {
let (randao, execution_optimistic) = state_id
.map_state_and_execution_optimistic(
&chain,
|state, execution_optimistic| {
let epoch = query.epoch.unwrap_or_else(|| state.current_epoch());
let randao = *state.get_randao_mix(epoch).map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"epoch out of range: {e:?}"
))
})?;
Ok((randao, execution_optimistic))
},
)?;
Ok(
api_types::GenericResponse::from(api_types::RandaoMix { randao })
.add_execution_optimistic(execution_optimistic),
)
})
},
);
// GET beacon/headers
//
// Note: this endpoint only returns information about blocks in the canonical chain. Given that
@@ -1168,6 +1240,51 @@ pub fn serve<T: BeaconChainTypes>(
})
});
// GET beacon/blinded_blocks/{block_id}
let get_beacon_blinded_block = eth_v1
.and(warp::path("beacon"))
.and(warp::path("blinded_blocks"))
.and(block_id_or_err)
.and(chain_filter.clone())
.and(warp::path::end())
.and(warp::header::optional::<api_types::Accept>("accept"))
.and_then(
|block_id: BlockId,
chain: Arc<BeaconChain<T>>,
accept_header: Option<api_types::Accept>| {
blocking_task(move || {
let (block, execution_optimistic) = block_id.blinded_block(&chain)?;
let fork_name = block
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/octet-stream")
.body(block.as_ssz_bytes().into())
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"failed to create response: {}",
e
))
}),
_ => {
// Post as a V2 endpoint so we return the fork version.
execution_optimistic_fork_versioned_response(
V2,
fork_name,
execution_optimistic,
block,
)
.map(|res| warp::reply::json(&res).into_response())
}
}
.map(|resp| add_consensus_version_header(resp, fork_name))
})
},
);
/*
* beacon/pool
*/
@@ -2681,7 +2798,12 @@ pub fn serve<T: BeaconChainTypes>(
.await
.map(|resp| warp::reply::json(&resp))
.map_err(|e| {
error!(log, "Error from connected relay"; "error" => ?e);
error!(
log,
"Relay error when registering validator(s)";
"num_registrations" => filtered_registration_data.len(),
"error" => ?e
);
// Forward the HTTP status code if we are able to, otherwise fall back
// to a server error.
if let eth2::Error::ServerMessage(message) = e {
@@ -2795,6 +2917,29 @@ pub fn serve<T: BeaconChainTypes>(
})
});
// GET lighthouse/ui/health
let get_lighthouse_ui_health = warp::path("lighthouse")
.and(warp::path("ui"))
.and(warp::path("health"))
.and(warp::path::end())
.and(system_info_filter)
.and(app_start_filter)
.and(data_dir_filter)
.and(network_globals.clone())
.and_then(
|sysinfo, app_start: std::time::Instant, data_dir, network_globals| {
blocking_json_task(move || {
let app_uptime = app_start.elapsed().as_secs() as u64;
Ok(api_types::GenericResponse::from(observe_system_health_bn(
sysinfo,
data_dir,
app_uptime,
network_globals,
)))
})
},
);
// GET lighthouse/syncing
let get_lighthouse_syncing = warp::path("lighthouse")
.and(warp::path("syncing"))
@@ -3213,10 +3358,12 @@ pub fn serve<T: BeaconChainTypes>(
.or(get_beacon_state_validators.boxed())
.or(get_beacon_state_committees.boxed())
.or(get_beacon_state_sync_committees.boxed())
.or(get_beacon_state_randao.boxed())
.or(get_beacon_headers.boxed())
.or(get_beacon_headers_block_id.boxed())
.or(get_beacon_block.boxed())
.or(get_beacon_block_attestations.boxed())
.or(get_beacon_blinded_block.boxed())
.or(get_beacon_block_root.boxed())
.or(get_beacon_pool_attestations.boxed())
.or(get_beacon_pool_attester_slashings.boxed())
@@ -3243,6 +3390,7 @@ pub fn serve<T: BeaconChainTypes>(
.or(get_validator_aggregate_attestation.boxed())
.or(get_validator_sync_committee_contribution.boxed())
.or(get_lighthouse_health.boxed())
.or(get_lighthouse_ui_health.boxed())
.or(get_lighthouse_syncing.boxed())
.or(get_lighthouse_nat.boxed())
.or(get_lighthouse_peers.boxed())
@@ -3262,6 +3410,7 @@ pub fn serve<T: BeaconChainTypes>(
.or(get_lighthouse_merge_readiness.boxed())
.or(get_events.boxed()),
)
.boxed()
.or(warp::post().and(
post_beacon_blocks
.boxed()
+7 -1
View File
@@ -1,5 +1,6 @@
use crate::metrics;
use beacon_chain::validator_monitor::{get_block_delay_ms, timestamp_now};
use beacon_chain::NotifyExecutionLayer;
use beacon_chain::{BeaconChain, BeaconChainTypes, BlockError, CountUnrealized};
use lighthouse_network::PubsubMessage;
use network::NetworkMessage;
@@ -53,7 +54,12 @@ pub async fn publish_block<T: BeaconChainTypes>(
metrics::observe_duration(&metrics::HTTP_API_BLOCK_BROADCAST_DELAY_TIMES, delay);
match chain
.process_block(block_root, block.clone(), CountUnrealized::True)
.process_block(
block_root,
block.clone(),
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
{
Ok(root) => {
+2
View File
@@ -2,6 +2,7 @@ use beacon_chain::{
test_utils::{BeaconChainHarness, EphemeralHarnessType},
BeaconChain, BeaconChainTypes,
};
use directory::DEFAULT_ROOT_DIR;
use eth2::{BeaconNodeHttpClient, Timeouts};
use http_api::{Config, Context};
use lighthouse_network::{
@@ -142,6 +143,7 @@ pub async fn create_api_server_on_port<T: BeaconChainTypes>(
allow_origin: None,
tls_config: None,
allow_sync_stalled: false,
data_dir: std::path::PathBuf::from(DEFAULT_ROOT_DIR),
spec_fork_name: None,
},
chain: Some(chain.clone()),
+110
View File
@@ -745,6 +745,36 @@ impl ApiTester {
self
}
pub async fn test_beacon_states_randao(self) -> Self {
for state_id in self.interesting_state_ids() {
let mut state_opt = state_id
.state(&self.chain)
.ok()
.map(|(state, _execution_optimistic)| state);
let epoch_opt = state_opt.as_ref().map(|state| state.current_epoch());
let result = self
.client
.get_beacon_states_randao(state_id.0, epoch_opt)
.await
.unwrap()
.map(|res| res.data);
if result.is_none() && state_opt.is_none() {
continue;
}
let state = state_opt.as_mut().expect("result should be none");
let randao_mix = state
.get_randao_mix(state.slot().epoch(E::slots_per_epoch()))
.unwrap();
assert_eq!(result.unwrap().randao, *randao_mix);
}
self
}
pub async fn test_beacon_headers_all_slots(self) -> Self {
for slot in 0..CHAIN_LENGTH {
let slot = Slot::from(slot);
@@ -1016,6 +1046,82 @@ impl ApiTester {
self
}
pub async fn test_beacon_blinded_blocks(self) -> Self {
for block_id in self.interesting_block_ids() {
let expected = block_id
.blinded_block(&self.chain)
.ok()
.map(|(block, _execution_optimistic)| block);
if let CoreBlockId::Slot(slot) = block_id.0 {
if expected.is_none() {
assert!(SKIPPED_SLOTS.contains(&slot.as_u64()));
} else {
assert!(!SKIPPED_SLOTS.contains(&slot.as_u64()));
}
}
// Check the JSON endpoint.
let json_result = self
.client
.get_beacon_blinded_blocks(block_id.0)
.await
.unwrap();
if let (Some(json), Some(expected)) = (&json_result, &expected) {
assert_eq!(&json.data, expected, "{:?}", block_id);
assert_eq!(
json.version,
Some(expected.fork_name(&self.chain.spec).unwrap())
);
} else {
assert_eq!(json_result, None);
assert_eq!(expected, None);
}
// Check the SSZ endpoint.
let ssz_result = self
.client
.get_beacon_blinded_blocks_ssz(block_id.0, &self.chain.spec)
.await
.unwrap();
assert_eq!(ssz_result.as_ref(), expected.as_ref(), "{:?}", block_id);
// Check that version headers are provided.
let url = self
.client
.get_beacon_blinded_blocks_path(block_id.0)
.unwrap();
let builders: Vec<fn(RequestBuilder) -> RequestBuilder> = vec![
|b| b,
|b| b.accept(Accept::Ssz),
|b| b.accept(Accept::Json),
|b| b.accept(Accept::Any),
];
for req_builder in builders {
let raw_res = self
.client
.get_response(url.clone(), req_builder)
.await
.optional()
.unwrap();
if let (Some(raw_res), Some(expected)) = (&raw_res, &expected) {
assert_eq!(
raw_res.fork_name_from_header().unwrap(),
Some(expected.fork_name(&self.chain.spec).unwrap())
);
} else {
assert!(raw_res.is_none());
assert_eq!(expected, None);
}
}
}
self
}
pub async fn test_beacon_blocks_attestations(self) -> Self {
for block_id in self.interesting_block_ids() {
let result = self
@@ -3696,6 +3802,8 @@ async fn beacon_get() {
.await
.test_beacon_states_validator_id()
.await
.test_beacon_states_randao()
.await
.test_beacon_headers_all_slots()
.await
.test_beacon_headers_all_parents()
@@ -3704,6 +3812,8 @@ async fn beacon_get() {
.await
.test_beacon_blocks()
.await
.test_beacon_blinded_blocks()
.await
.test_beacon_blocks_attestations()
.await
.test_beacon_blocks_root()
+9 -3
View File
@@ -130,6 +130,9 @@ pub struct Config {
/// Whether metrics are enabled.
pub metrics_enabled: bool,
/// Whether light client protocols should be enabled.
pub enable_light_client_server: bool,
}
impl Default for Config {
@@ -207,6 +210,7 @@ impl Default for Config {
shutdown_after_sync: false,
topics: Vec::new(),
metrics_enabled: false,
enable_light_client_server: false,
}
}
}
@@ -284,9 +288,11 @@ impl From<u8> for NetworkLoad {
/// Return a Lighthouse specific `GossipsubConfig` where the `message_id_fn` depends on the current fork.
pub fn gossipsub_config(network_load: u8, fork_context: Arc<ForkContext>) -> GossipsubConfig {
// The function used to generate a gossipsub message id
// We use the first 8 bytes of SHA256(data) for content addressing
let fast_gossip_message_id =
|message: &RawGossipsubMessage| FastMessageId::from(&Sha256::digest(&message.data)[..8]);
// We use the first 8 bytes of SHA256(topic, data) for content addressing
let fast_gossip_message_id = |message: &RawGossipsubMessage| {
let data = [message.topic.as_str().as_bytes(), &message.data].concat();
FastMessageId::from(&Sha256::digest(data)[..8])
};
fn prefix(
prefix: [u8; 4],
message: &GossipsubMessage,
@@ -834,6 +834,17 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
// Map each subnet query's min_ttl to the set of ENR's returned for that subnet.
queries.iter().for_each(|query| {
let query_str = match query.subnet {
Subnet::Attestation(_) => "attestation",
Subnet::SyncCommittee(_) => "sync_committee",
};
if let Some(v) = metrics::get_int_counter(
&metrics::TOTAL_SUBNET_QUERIES,
&[query_str],
) {
v.inc();
}
// A subnet query has completed. Add back to the queue, incrementing retries.
self.add_subnet_query(query.subnet, query.min_ttl, query.retries + 1);
@@ -845,6 +856,12 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
.filter(|enr| subnet_predicate(enr))
.map(|enr| enr.peer_id())
.for_each(|peer_id| {
if let Some(v) = metrics::get_int_counter(
&metrics::SUBNET_PEERS_FOUND,
&[query_str],
) {
v.inc();
}
let other_min_ttl = mapped_results.get_mut(&peer_id);
// map peer IDs to the min_ttl furthest in the future
@@ -112,6 +112,19 @@ lazy_static! {
&["client"]
);
pub static ref SUBNET_PEERS_FOUND: Result<IntCounterVec> =
try_create_int_counter_vec(
"discovery_query_peers_found",
"Total number of peers found in attestation subnets and sync subnets",
&["type"]
);
pub static ref TOTAL_SUBNET_QUERIES: Result<IntCounterVec> =
try_create_int_counter_vec(
"discovery_total_queries",
"Total number of discovery subnet queries",
&["type"]
);
/*
* Inbound/Outbound peers
*/
@@ -405,7 +405,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
debug!(self.log, "Identified Peer"; "peer" => %peer_id,
"protocol_version" => &info.protocol_version,
"agent_version" => &info.agent_version,
"listening_ addresses" => ?info.listen_addrs,
"listening_addresses" => ?info.listen_addrs,
"observed_address" => ?info.observed_addr,
"protocols" => ?info.protocols
);
@@ -502,6 +502,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
Protocol::BlocksByRange => PeerAction::MidToleranceError,
Protocol::BlocksByRoot => PeerAction::MidToleranceError,
Protocol::BlobsByRange => PeerAction::MidToleranceError,
Protocol::LightClientBootstrap => PeerAction::LowToleranceError,
Protocol::BlobsByRoot => PeerAction::MidToleranceError,
Protocol::Goodbye => PeerAction::LowToleranceError,
Protocol::MetaData => PeerAction::LowToleranceError,
@@ -521,6 +522,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
Protocol::BlobsByRange => return,
Protocol::BlobsByRoot => return,
Protocol::Goodbye => return,
Protocol::LightClientBootstrap => return,
Protocol::MetaData => PeerAction::LowToleranceError,
Protocol::Status => PeerAction::LowToleranceError,
}
@@ -537,6 +539,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
Protocol::BlocksByRoot => PeerAction::MidToleranceError,
Protocol::BlobsByRange => PeerAction::MidToleranceError,
Protocol::BlobsByRoot => PeerAction::MidToleranceError,
Protocol::LightClientBootstrap => return,
Protocol::Goodbye => return,
Protocol::MetaData => return,
Protocol::Status => return,
@@ -139,7 +139,7 @@ impl<TSpec: EthSpec> NetworkBehaviour for PeerManager<TSpec> {
// TODO: directly emit the ban event?
BanResult::BadScore => {
// This is a faulty state
error!(self.log, "Connected to a banned peer, re-banning"; "peer_id" => %peer_id);
error!(self.log, "Connected to a banned peer. Re-banning"; "peer_id" => %peer_id);
// Reban the peer
self.goodbye_peer(peer_id, GoodbyeReason::Banned, ReportSource::PeerManager);
return;
@@ -15,10 +15,11 @@ use std::io::{Read, Write};
use std::marker::PhantomData;
use std::sync::Arc;
use tokio_util::codec::{Decoder, Encoder};
use types::light_client_bootstrap::LightClientBootstrap;
use types::{
BlobsSidecar, EthSpec, ForkContext, ForkName, SignedBeaconBlock, SignedBeaconBlockAltair,
SignedBeaconBlockAndBlobsSidecar, SignedBeaconBlockBase, SignedBeaconBlockCapella,
SignedBeaconBlockEip4844, SignedBeaconBlockMerge,
BlobsSidecar, EthSpec, ForkContext, ForkName, Hash256, SignedBeaconBlock,
SignedBeaconBlockAltair, SignedBeaconBlockAndBlobsSidecar, SignedBeaconBlockBase,
SignedBeaconBlockCapella, SignedBeaconBlockEip4844, SignedBeaconBlockMerge,
};
use unsigned_varint::codec::Uvi;
@@ -73,6 +74,7 @@ impl<TSpec: EthSpec> Encoder<RPCCodedResponse<TSpec>> for SSZSnappyInboundCodec<
RPCResponse::BlocksByRoot(res) => res.as_ssz_bytes(),
RPCResponse::BlobsByRange(res) => res.as_ssz_bytes(),
RPCResponse::BlobsByRoot(res) => res.as_ssz_bytes(),
RPCResponse::LightClientBootstrap(res) => res.as_ssz_bytes(),
RPCResponse::Pong(res) => res.data.as_ssz_bytes(),
RPCResponse::MetaData(res) =>
// Encode the correct version of the MetaData response based on the negotiated version.
@@ -235,6 +237,7 @@ impl<TSpec: EthSpec> Encoder<OutboundRequest<TSpec>> for SSZSnappyOutboundCodec<
OutboundRequest::BlobsByRoot(req) => req.block_roots.as_ssz_bytes(),
OutboundRequest::Ping(req) => req.as_ssz_bytes(),
OutboundRequest::MetaData(_) => return Ok(()), // no metadata to encode
OutboundRequest::LightClientBootstrap(req) => req.as_ssz_bytes(),
};
// SSZ encoded bytes should be within `max_packet_size`
if bytes.len() > self.max_packet_size {
@@ -495,7 +498,11 @@ fn handle_v1_request<T: EthSpec>(
Protocol::Ping => Ok(Some(InboundRequest::Ping(Ping {
data: u64::from_ssz_bytes(decoded_buffer)?,
}))),
Protocol::LightClientBootstrap => Ok(Some(InboundRequest::LightClientBootstrap(
LightClientBootstrapRequest {
root: Hash256::from_ssz_bytes(decoded_buffer)?,
},
))),
// MetaData requests return early from InboundUpgrade and do not reach the decoder.
// Handle this case just for completeness.
Protocol::MetaData => {
@@ -605,6 +612,9 @@ fn handle_v1_response<T: EthSpec>(
Protocol::MetaData => Ok(Some(RPCResponse::MetaData(MetaData::V1(
MetaDataV1::from_ssz_bytes(decoded_buffer)?,
)))),
Protocol::LightClientBootstrap => Ok(Some(RPCResponse::LightClientBootstrap(
LightClientBootstrap::from_ssz_bytes(decoded_buffer)?,
))),
}
}
@@ -966,6 +976,9 @@ mod tests {
OutboundRequest::MetaData(metadata) => {
assert_eq!(decoded, InboundRequest::MetaData(metadata))
}
OutboundRequest::LightClientBootstrap(bootstrap) => {
assert_eq!(decoded, InboundRequest::LightClientBootstrap(bootstrap))
}
}
}
}
@@ -285,7 +285,7 @@ where
} else {
if !matches!(response, RPCCodedResponse::StreamTermination(..)) {
// the stream is closed after sending the expected number of responses
trace!(self.log, "Inbound stream has expired, response not sent";
trace!(self.log, "Inbound stream has expired. Response not sent";
"response" => %response, "id" => inbound_id);
}
return;
@@ -12,9 +12,11 @@ use std::ops::Deref;
use std::sync::Arc;
use strum::IntoStaticStr;
use superstruct::superstruct;
use types::blobs_sidecar::BlobsSidecar;
use types::SignedBeaconBlockAndBlobsSidecar;
use types::{Epoch, EthSpec, Hash256, SignedBeaconBlock, Slot};
use types::{
blobs_sidecar::BlobsSidecar, light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec,
Hash256, SignedBeaconBlock, Slot,
};
/// Maximum number of blocks in a single request.
pub type MaxRequestBlocks = U1024;
@@ -268,6 +270,9 @@ pub enum RPCResponse<T: EthSpec> {
/// A response to a get BLOBS_BY_RANGE request
BlobsByRange(Arc<BlobsSidecar<T>>),
/// A response to a get LIGHTCLIENT_BOOTSTRAP request.
LightClientBootstrap(LightClientBootstrap<T>),
/// A response to a get BLOBS_BY_ROOT request.
BlobsByRoot(Arc<SignedBeaconBlockAndBlobsSidecar<T>>),
@@ -307,6 +312,12 @@ pub enum RPCCodedResponse<T: EthSpec> {
StreamTermination(ResponseTermination),
}
/// Request a light_client_bootstrap for lightclients peers.
#[derive(Encode, Decode, Clone, Debug, PartialEq)]
pub struct LightClientBootstrapRequest {
pub root: Hash256,
}
/// The code assigned to an erroneous `RPCResponse`.
#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
@@ -357,6 +368,7 @@ impl<T: EthSpec> RPCCodedResponse<T> {
RPCResponse::BlobsByRoot(_) => true,
RPCResponse::Pong(_) => false,
RPCResponse::MetaData(_) => false,
RPCResponse::LightClientBootstrap(_) => false,
},
RPCCodedResponse::Error(_, _) => true,
// Stream terminations are part of responses that have chunks
@@ -393,6 +405,7 @@ impl<T: EthSpec> RPCResponse<T> {
RPCResponse::BlobsByRoot(_) => Protocol::BlobsByRoot,
RPCResponse::Pong(_) => Protocol::Ping,
RPCResponse::MetaData(_) => Protocol::MetaData,
RPCResponse::LightClientBootstrap(_) => Protocol::LightClientBootstrap,
}
}
}
@@ -438,6 +451,9 @@ impl<T: EthSpec> std::fmt::Display for RPCResponse<T> {
}
RPCResponse::Pong(ping) => write!(f, "Pong: {}", ping.data),
RPCResponse::MetaData(metadata) => write!(f, "Metadata: {}", metadata.seq_number()),
RPCResponse::LightClientBootstrap(bootstrap) => {
write!(f, "LightClientBootstrap Slot: {}", bootstrap.header.slot)
}
}
}
}
+11 -3
View File
@@ -27,8 +27,8 @@ pub(crate) use protocol::{InboundRequest, RPCProtocol};
use crate::rpc::methods::MAX_REQUEST_BLOBS_SIDECARS;
pub use handler::SubstreamId;
pub use methods::{
BlocksByRangeRequest, BlocksByRootRequest, GoodbyeReason, MaxRequestBlocks,
RPCResponseErrorCode, ResponseTermination, StatusMessage, MAX_REQUEST_BLOCKS,
BlocksByRangeRequest, BlocksByRootRequest, GoodbyeReason, LightClientBootstrapRequest,
MaxRequestBlocks, RPCResponseErrorCode, ResponseTermination, StatusMessage, MAX_REQUEST_BLOCKS,
};
pub(crate) use outbound::OutboundRequest;
pub use protocol::{max_rpc_size, Protocol, RPCError};
@@ -109,18 +109,24 @@ pub struct RPC<Id: ReqId, TSpec: EthSpec> {
/// Queue of events to be processed.
events: Vec<NetworkBehaviourAction<RPCMessage<Id, TSpec>, RPCHandler<Id, TSpec>>>,
fork_context: Arc<ForkContext>,
enable_light_client_server: bool,
/// Slog logger for RPC behaviour.
log: slog::Logger,
}
impl<Id: ReqId, TSpec: EthSpec> RPC<Id, TSpec> {
pub fn new(fork_context: Arc<ForkContext>, log: slog::Logger) -> Self {
pub fn new(
fork_context: Arc<ForkContext>,
enable_light_client_server: bool,
log: slog::Logger,
) -> Self {
let log = log.new(o!("service" => "libp2p_rpc"));
let limiter = RPCRateLimiterBuilder::new()
.n_every(Protocol::MetaData, 2, Duration::from_secs(5))
.n_every(Protocol::Ping, 2, Duration::from_secs(10))
.n_every(Protocol::Status, 5, Duration::from_secs(15))
.one_every(Protocol::Goodbye, Duration::from_secs(10))
.one_every(Protocol::LightClientBootstrap, Duration::from_secs(10))
.n_every(
Protocol::BlocksByRange,
methods::MAX_REQUEST_BLOCKS,
@@ -139,6 +145,7 @@ impl<Id: ReqId, TSpec: EthSpec> RPC<Id, TSpec> {
limiter,
events: Vec::new(),
fork_context,
enable_light_client_server,
log,
}
}
@@ -195,6 +202,7 @@ where
RPCProtocol {
fork_context: self.fork_context.clone(),
max_rpc_size: max_rpc_size(&self.fork_context),
enable_light_client_server: self.enable_light_client_server,
phantom: PhantomData,
},
(),
@@ -40,6 +40,7 @@ pub enum OutboundRequest<TSpec: EthSpec> {
BlocksByRoot(BlocksByRootRequest),
BlobsByRange(BlobsByRangeRequest),
BlobsByRoot(BlobsByRootRequest),
LightClientBootstrap(LightClientBootstrapRequest),
Ping(Ping),
MetaData(PhantomData<TSpec>),
}
@@ -96,9 +97,12 @@ impl<TSpec: EthSpec> OutboundRequest<TSpec> {
ProtocolId::new(Protocol::MetaData, Version::V2, Encoding::SSZSnappy),
ProtocolId::new(Protocol::MetaData, Version::V1, Encoding::SSZSnappy),
],
// Note: This match arm is technically unreachable as we only respond to light client requests
// that we generate from the beacon state.
// We do not make light client rpc requests from the beacon node
OutboundRequest::LightClientBootstrap(_) => vec![],
}
}
/* These functions are used in the handler for stream management */
/// Number of responses expected for this request.
@@ -112,6 +116,7 @@ impl<TSpec: EthSpec> OutboundRequest<TSpec> {
OutboundRequest::BlobsByRoot(req) => req.block_roots.len() as u64,
OutboundRequest::Ping(_) => 1,
OutboundRequest::MetaData(_) => 1,
OutboundRequest::LightClientBootstrap(_) => 1,
}
}
@@ -126,6 +131,7 @@ impl<TSpec: EthSpec> OutboundRequest<TSpec> {
OutboundRequest::BlobsByRoot(_) => Protocol::BlobsByRoot,
OutboundRequest::Ping(_) => Protocol::Ping,
OutboundRequest::MetaData(_) => Protocol::MetaData,
OutboundRequest::LightClientBootstrap(_) => Protocol::LightClientBootstrap,
}
}
@@ -139,6 +145,7 @@ impl<TSpec: EthSpec> OutboundRequest<TSpec> {
OutboundRequest::BlocksByRoot(_) => ResponseTermination::BlocksByRoot,
OutboundRequest::BlobsByRange(_) => ResponseTermination::BlobsByRange,
OutboundRequest::BlobsByRoot(_) => ResponseTermination::BlobsByRoot,
OutboundRequest::LightClientBootstrap(_) => unreachable!(),
OutboundRequest::Status(_) => unreachable!(),
OutboundRequest::Goodbye(_) => unreachable!(),
OutboundRequest::Ping(_) => unreachable!(),
@@ -198,6 +205,9 @@ impl<TSpec: EthSpec> std::fmt::Display for OutboundRequest<TSpec> {
OutboundRequest::BlobsByRoot(req) => write!(f, "Blobs by root: {:?}", req),
OutboundRequest::Ping(ping) => write!(f, "Ping: {}", ping.data),
OutboundRequest::MetaData(_) => write!(f, "MetaData request"),
OutboundRequest::LightClientBootstrap(bootstrap) => {
write!(f, "Lightclient Bootstrap: {}", bootstrap.root)
}
}
}
}
@@ -181,6 +181,8 @@ pub enum Protocol {
Ping,
/// The `MetaData` protocol name.
MetaData,
/// The `LightClientBootstrap` protocol name.
LightClientBootstrap,
}
/// RPC Versions
@@ -209,6 +211,7 @@ impl std::fmt::Display for Protocol {
Protocol::BlobsByRoot => "beacon_block_and_blobs_sidecar_by_root",
Protocol::Ping => "ping",
Protocol::MetaData => "metadata",
Protocol::LightClientBootstrap => "light_client_bootstrap",
};
f.write_str(repr)
}
@@ -237,6 +240,7 @@ impl std::fmt::Display for Version {
pub struct RPCProtocol<TSpec: EthSpec> {
pub fork_context: Arc<ForkContext>,
pub max_rpc_size: usize,
pub enable_light_client_server: bool,
pub phantom: PhantomData<TSpec>,
}
@@ -246,7 +250,7 @@ impl<TSpec: EthSpec> UpgradeInfo for RPCProtocol<TSpec> {
/// The list of supported RPC protocols for Lighthouse.
fn protocol_info(&self) -> Self::InfoIter {
vec![
let mut supported_protocols = vec![
ProtocolId::new(Protocol::Status, Version::V1, Encoding::SSZSnappy),
ProtocolId::new(Protocol::Goodbye, Version::V1, Encoding::SSZSnappy),
// V2 variants have higher preference then V1
@@ -257,7 +261,15 @@ impl<TSpec: EthSpec> UpgradeInfo for RPCProtocol<TSpec> {
ProtocolId::new(Protocol::Ping, Version::V1, Encoding::SSZSnappy),
ProtocolId::new(Protocol::MetaData, Version::V2, Encoding::SSZSnappy),
ProtocolId::new(Protocol::MetaData, Version::V1, Encoding::SSZSnappy),
]
];
if self.enable_light_client_server {
supported_protocols.push(ProtocolId::new(
Protocol::LightClientBootstrap,
Version::V1,
Encoding::SSZSnappy,
));
}
supported_protocols
}
}
@@ -326,6 +338,10 @@ impl ProtocolId {
<Ping as Encode>::ssz_fixed_len(),
<Ping as Encode>::ssz_fixed_len(),
),
Protocol::LightClientBootstrap => RpcLimits::new(
<LightClientBootstrapRequest as Encode>::ssz_fixed_len(),
<LightClientBootstrapRequest as Encode>::ssz_fixed_len(),
),
Protocol::MetaData => RpcLimits::new(0, 0), // Metadata requests are empty
}
}
@@ -353,6 +369,10 @@ impl ProtocolId {
<MetaDataV1<T> as Encode>::ssz_fixed_len(),
<MetaDataV2<T> as Encode>::ssz_fixed_len(),
),
Protocol::LightClientBootstrap => RpcLimits::new(
<LightClientBootstrapRequest as Encode>::ssz_fixed_len(),
<LightClientBootstrapRequest as Encode>::ssz_fixed_len(),
),
}
}
@@ -460,67 +480,13 @@ pub enum InboundRequest<TSpec: EthSpec> {
BlocksByRoot(BlocksByRootRequest),
BlobsByRange(BlobsByRangeRequest),
BlobsByRoot(BlobsByRootRequest),
LightClientBootstrap(LightClientBootstrapRequest),
Ping(Ping),
MetaData(PhantomData<TSpec>),
}
impl<TSpec: EthSpec> UpgradeInfo for InboundRequest<TSpec> {
type Info = ProtocolId;
type InfoIter = Vec<Self::Info>;
// add further protocols as we support more encodings/versions
fn protocol_info(&self) -> Self::InfoIter {
self.supported_protocols()
}
}
/// Implements the encoding per supported protocol for `RPCRequest`.
impl<TSpec: EthSpec> InboundRequest<TSpec> {
pub fn supported_protocols(&self) -> Vec<ProtocolId> {
match self {
// add more protocols when versions/encodings are supported
InboundRequest::Status(_) => vec![ProtocolId::new(
Protocol::Status,
Version::V1,
Encoding::SSZSnappy,
)],
InboundRequest::Goodbye(_) => vec![ProtocolId::new(
Protocol::Goodbye,
Version::V1,
Encoding::SSZSnappy,
)],
InboundRequest::BlocksByRange(_) => vec![
// V2 has higher preference when negotiating a stream
ProtocolId::new(Protocol::BlocksByRange, Version::V2, Encoding::SSZSnappy),
ProtocolId::new(Protocol::BlocksByRange, Version::V1, Encoding::SSZSnappy),
],
InboundRequest::BlocksByRoot(_) => vec![
// V2 has higher preference when negotiating a stream
ProtocolId::new(Protocol::BlocksByRoot, Version::V2, Encoding::SSZSnappy),
ProtocolId::new(Protocol::BlocksByRoot, Version::V1, Encoding::SSZSnappy),
],
InboundRequest::BlobsByRange(_) => vec![ProtocolId::new(
Protocol::BlobsByRange,
Version::V1,
Encoding::SSZSnappy,
)],
InboundRequest::BlobsByRoot(_) => vec![ProtocolId::new(
Protocol::BlobsByRoot,
Version::V1,
Encoding::SSZSnappy,
)],
InboundRequest::Ping(_) => vec![ProtocolId::new(
Protocol::Ping,
Version::V1,
Encoding::SSZSnappy,
)],
InboundRequest::MetaData(_) => vec![
ProtocolId::new(Protocol::MetaData, Version::V2, Encoding::SSZSnappy),
ProtocolId::new(Protocol::MetaData, Version::V1, Encoding::SSZSnappy),
],
}
}
/* These functions are used in the handler for stream management */
/// Number of responses expected for this request.
@@ -534,6 +500,7 @@ impl<TSpec: EthSpec> InboundRequest<TSpec> {
InboundRequest::BlobsByRoot(req) => req.block_roots.len() as u64,
InboundRequest::Ping(_) => 1,
InboundRequest::MetaData(_) => 1,
InboundRequest::LightClientBootstrap(_) => 1,
}
}
@@ -548,6 +515,7 @@ impl<TSpec: EthSpec> InboundRequest<TSpec> {
InboundRequest::BlobsByRoot(_) => Protocol::BlobsByRoot,
InboundRequest::Ping(_) => Protocol::Ping,
InboundRequest::MetaData(_) => Protocol::MetaData,
InboundRequest::LightClientBootstrap(_) => Protocol::LightClientBootstrap,
}
}
@@ -565,6 +533,7 @@ impl<TSpec: EthSpec> InboundRequest<TSpec> {
InboundRequest::Goodbye(_) => unreachable!(),
InboundRequest::Ping(_) => unreachable!(),
InboundRequest::MetaData(_) => unreachable!(),
InboundRequest::LightClientBootstrap(_) => unreachable!(),
}
}
}
@@ -670,6 +639,9 @@ impl<TSpec: EthSpec> std::fmt::Display for InboundRequest<TSpec> {
InboundRequest::BlobsByRoot(req) => write!(f, "Blobs by root: {:?}", req),
InboundRequest::Ping(ping) => write!(f, "Ping: {}", ping.data),
InboundRequest::MetaData(_) => write!(f, "MetaData request"),
InboundRequest::LightClientBootstrap(bootstrap) => {
write!(f, "LightClientBootstrap: {}", bootstrap.root)
}
}
}
}
@@ -77,6 +77,8 @@ pub struct RPCRateLimiter {
blbrange_rl: Limiter<PeerId>,
/// BlobsByRoot rate limiter.
blbroot_rl: Limiter<PeerId>,
/// LightClientBootstrap rate limiter.
lcbootstrap_rl: Limiter<PeerId>,
}
/// Error type for non conformant requests
@@ -106,6 +108,8 @@ pub struct RPCRateLimiterBuilder {
blbrange_quota: Option<Quota>,
/// Quota for the BlobsByRoot protocol.
blbroot_quota: Option<Quota>,
/// Quota for the LightClientBootstrap protocol.
lcbootstrap_quota: Option<Quota>,
}
impl RPCRateLimiterBuilder {
@@ -126,6 +130,7 @@ impl RPCRateLimiterBuilder {
Protocol::BlocksByRoot => self.bbroots_quota = q,
Protocol::BlobsByRange => self.blbrange_quota = q,
Protocol::BlobsByRoot => self.blbroot_quota = q,
Protocol::LightClientBootstrap => self.lcbootstrap_quota = q,
}
self
}
@@ -165,6 +170,9 @@ impl RPCRateLimiterBuilder {
let bbrange_quota = self
.bbrange_quota
.ok_or("BlocksByRange quota not specified")?;
let lcbootstrap_quote = self
.lcbootstrap_quota
.ok_or("LightClientBootstrap quota not specified")?;
let blbrange_quota = self
.blbrange_quota
@@ -183,6 +191,7 @@ impl RPCRateLimiterBuilder {
let bbrange_rl = Limiter::from_quota(bbrange_quota)?;
let blbrange_rl = Limiter::from_quota(blbrange_quota)?;
let blbroot_rl = Limiter::from_quota(blbroots_quota)?;
let lcbootstrap_rl = Limiter::from_quota(lcbootstrap_quote)?;
// check for peers to prune every 30 seconds, starting in 30 seconds
let prune_every = tokio::time::Duration::from_secs(30);
@@ -198,6 +207,7 @@ impl RPCRateLimiterBuilder {
bbrange_rl,
blbrange_rl,
blbroot_rl,
lcbootstrap_rl,
init_time: Instant::now(),
})
}
@@ -223,6 +233,7 @@ impl RPCRateLimiter {
Protocol::BlocksByRoot => &mut self.bbroots_rl,
Protocol::BlobsByRange => &mut self.blbrange_rl,
Protocol::BlobsByRoot => &mut self.blbroot_rl,
Protocol::LightClientBootstrap => &mut self.lcbootstrap_rl,
};
check(limiter)
}
@@ -1,13 +1,14 @@
use std::sync::Arc;
use libp2p::core::connection::ConnectionId;
use types::light_client_bootstrap::LightClientBootstrap;
use types::{BlobsSidecar, EthSpec, SignedBeaconBlock};
use crate::rpc::methods::{BlobsByRangeRequest, BlobsByRootRequest};
use crate::rpc::{
methods::{
BlocksByRangeRequest, BlocksByRootRequest, OldBlocksByRangeRequest, RPCCodedResponse,
RPCResponse, ResponseTermination, StatusMessage,
BlocksByRangeRequest, BlocksByRootRequest, LightClientBootstrapRequest,
OldBlocksByRangeRequest, RPCCodedResponse, RPCResponse, ResponseTermination, StatusMessage,
},
OutboundRequest, SubstreamId,
};
@@ -38,6 +39,8 @@ pub enum Request {
BlobsByRange(BlobsByRangeRequest),
/// A request blocks root request.
BlocksByRoot(BlocksByRootRequest),
// light client bootstrap request
LightClientBootstrap(LightClientBootstrapRequest),
/// A request blobs root request.
BlobsByRoot(BlobsByRootRequest),
}
@@ -54,6 +57,7 @@ impl<TSpec: EthSpec> std::convert::From<Request> for OutboundRequest<TSpec> {
})
}
Request::BlobsByRange(r) => OutboundRequest::BlobsByRange(r),
Request::LightClientBootstrap(b) => OutboundRequest::LightClientBootstrap(b),
Request::BlobsByRoot(r) => OutboundRequest::BlobsByRoot(r),
Request::Status(s) => OutboundRequest::Status(s),
}
@@ -76,6 +80,8 @@ pub enum Response<TSpec: EthSpec> {
BlobsByRange(Option<Arc<BlobsSidecar<TSpec>>>),
/// A response to a get BLOCKS_BY_ROOT request.
BlocksByRoot(Option<Arc<SignedBeaconBlock<TSpec>>>),
/// A response to a LightClientUpdate request.
LightClientBootstrap(LightClientBootstrap<TSpec>),
/// A response to a get BLOBS_BY_ROOT request.
BlobsByRoot(Option<Arc<SignedBeaconBlockAndBlobsSidecar<TSpec>>>),
}
@@ -100,6 +106,9 @@ impl<TSpec: EthSpec> std::convert::From<Response<TSpec>> for RPCCodedResponse<TS
None => RPCCodedResponse::StreamTermination(ResponseTermination::BlobsByRange),
},
Response::Status(s) => RPCCodedResponse::Success(RPCResponse::Status(s)),
Response::LightClientBootstrap(b) => {
RPCCodedResponse::Success(RPCResponse::LightClientBootstrap(b))
}
}
}
}
@@ -8,7 +8,6 @@ use libp2p::gossipsub::subscription_filter::{
};
use libp2p::gossipsub::Gossipsub as BaseGossipsub;
use libp2p::identify::Identify;
use libp2p::swarm::NetworkBehaviour;
use libp2p::NetworkBehaviour;
use types::EthSpec;
@@ -262,7 +262,11 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
(gossipsub, update_gossipsub_scores)
};
let eth2_rpc = RPC::new(ctx.fork_context.clone(), log.clone());
let eth2_rpc = RPC::new(
ctx.fork_context.clone(),
config.enable_light_client_server,
log.clone(),
);
let discovery = {
// Build and start the discovery sub-behaviour
@@ -981,6 +985,9 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
Request::Status(_) => {
metrics::inc_counter_vec(&metrics::TOTAL_RPC_REQUESTS, &["status"])
}
Request::LightClientBootstrap(_) => {
metrics::inc_counter_vec(&metrics::TOTAL_RPC_REQUESTS, &["light_client_bootstrap"])
}
Request::BlocksByRange { .. } => {
metrics::inc_counter_vec(&metrics::TOTAL_RPC_REQUESTS, &["blocks_by_range"])
}
@@ -1269,6 +1276,14 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
self.build_request(peer_request_id, peer_id, Request::BlobsByRoot(req));
Some(event)
}
InboundRequest::LightClientBootstrap(req) => {
let event = self.build_request(
peer_request_id,
peer_id,
Request::LightClientBootstrap(req),
);
Some(event)
}
}
}
Ok(RPCReceived::Response(id, resp)) => {
@@ -1302,6 +1317,10 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
RPCResponse::BlobsByRoot(resp) => {
self.build_response(id, peer_id, Response::BlobsByRoot(Some(resp)))
}
// Should never be reached
RPCResponse::LightClientBootstrap(bootstrap) => {
self.build_response(id, peer_id, Response::LightClientBootstrap(bootstrap))
}
}
}
Ok(RPCReceived::EndOfStream(id, termination)) => {
@@ -74,6 +74,17 @@ impl SyncState {
}
}
pub fn is_syncing_finalized(&self) -> bool {
match self {
SyncState::SyncingFinalized { .. } => true,
SyncState::SyncingHead { .. } => false,
SyncState::SyncTransition => false,
SyncState::BackFillSyncing { .. } => false,
SyncState::Synced => false,
SyncState::Stalled => false,
}
}
/// Returns true if the node is synced.
///
/// NOTE: We consider the node synced if it is fetching old historical blocks.
@@ -41,11 +41,12 @@
use crate::sync::manager::BlockProcessType;
use crate::{metrics, service::NetworkMessage, sync::SyncMessage};
use beacon_chain::parking_lot::Mutex;
use beacon_chain::{BeaconChain, BeaconChainTypes, GossipVerifiedBlock};
use beacon_chain::{BeaconChain, BeaconChainTypes, GossipVerifiedBlock, NotifyExecutionLayer};
use derivative::Derivative;
use futures::stream::{Stream, StreamExt};
use futures::task::Poll;
use lighthouse_network::rpc::methods::{BlobsByRangeRequest, BlobsByRootRequest};
use lighthouse_network::rpc::LightClientBootstrapRequest;
use lighthouse_network::{
rpc::{BlocksByRangeRequest, BlocksByRootRequest, StatusMessage},
Client, MessageId, NetworkGlobals, PeerId, PeerRequestId,
@@ -171,6 +172,10 @@ const MAX_BLOCK_AND_BLOBS_BY_ROOTS_QUEUE_LEN: usize = 1_024;
/// is activated.
const MAX_BLS_TO_EXECUTION_CHANGE_QUEUE_LEN: usize = 16_384;
/// The maximum number of queued `LightClientBootstrapRequest` objects received from the network RPC that
/// will be stored before we start dropping them.
const MAX_LIGHT_CLIENT_BOOTSTRAP_QUEUE_LEN: usize = 1_024;
/// The name of the manager tokio task.
const MANAGER_TASK_NAME: &str = "beacon_processor_manager";
@@ -213,6 +218,7 @@ pub const BLOCKS_BY_RANGE_REQUEST: &str = "blocks_by_range_request";
pub const BLOCKS_BY_ROOTS_REQUEST: &str = "blocks_by_roots_request";
pub const BLOBS_BY_RANGE_REQUEST: &str = "blobs_by_range_request";
pub const BLOBS_BY_ROOTS_REQUEST: &str = "blobs_by_roots_request";
pub const LIGHT_CLIENT_BOOTSTRAP_REQUEST: &str = "light_client_bootstrap";
pub const UNKNOWN_BLOCK_ATTESTATION: &str = "unknown_block_attestation";
pub const UNKNOWN_BLOCK_AGGREGATE: &str = "unknown_block_aggregate";
pub const GOSSIP_BLS_TO_EXECUTION_CHANGE: &str = "gossip_bls_to_execution_change";
@@ -627,6 +633,22 @@ impl<T: BeaconChainTypes> WorkEvent<T> {
}
}
/// Create a new work event to process `LightClientBootstrap`s from the RPC network.
pub fn lightclient_bootstrap_request(
peer_id: PeerId,
request_id: PeerRequestId,
request: LightClientBootstrapRequest,
) -> Self {
Self {
drop_during_sync: true,
work: Work::LightClientBootstrapRequest {
peer_id,
request_id,
request,
},
}
}
pub fn blobs_by_root_request(
peer_id: PeerId,
request_id: PeerRequestId,
@@ -835,6 +857,11 @@ pub enum Work<T: BeaconChainTypes> {
peer_id: PeerId,
bls_to_execution_change: Box<SignedBlsToExecutionChange>,
},
LightClientBootstrapRequest {
peer_id: PeerId,
request_id: PeerRequestId,
request: LightClientBootstrapRequest,
},
BlobsByRootsRequest {
peer_id: PeerId,
request_id: PeerRequestId,
@@ -865,6 +892,7 @@ impl<T: BeaconChainTypes> Work<T> {
Work::BlocksByRootsRequest { .. } => BLOCKS_BY_ROOTS_REQUEST,
Work::BlobsByRangeRequest { .. } => BLOBS_BY_RANGE_REQUEST,
Work::BlobsByRootsRequest { .. } => BLOBS_BY_ROOTS_REQUEST,
Work::LightClientBootstrapRequest { .. } => LIGHT_CLIENT_BOOTSTRAP_REQUEST,
Work::UnknownBlockAttestation { .. } => UNKNOWN_BLOCK_ATTESTATION,
Work::UnknownBlockAggregate { .. } => UNKNOWN_BLOCK_AGGREGATE,
Work::GossipBlsToExecutionChange { .. } => GOSSIP_BLS_TO_EXECUTION_CHANGE,
@@ -1017,6 +1045,7 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
let mut gossip_bls_to_execution_change_queue =
FifoQueue::new(MAX_BLS_TO_EXECUTION_CHANGE_QUEUE_LEN);
let mut lcbootstrap_queue = FifoQueue::new(MAX_LIGHT_CLIENT_BOOTSTRAP_QUEUE_LEN);
// Channels for sending work to the re-process scheduler (`work_reprocessing_tx`) and to
// receive them back once they are ready (`ready_work_rx`).
let (ready_work_tx, ready_work_rx) = mpsc::channel(MAX_SCHEDULED_WORK_QUEUE_LEN);
@@ -1266,6 +1295,8 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
} else if let Some(item) = backfill_chain_segment.pop() {
self.spawn_worker(item, toolbox);
// This statement should always be the final else statement.
} else if let Some(item) = lcbootstrap_queue.pop() {
self.spawn_worker(item, toolbox);
} else {
// Let the journal know that a worker is freed and there's nothing else
// for it to do.
@@ -1372,6 +1403,9 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
Work::BlobsByRangeRequest { .. } => {
blbrange_queue.push(work, work_id, &self.log)
}
Work::LightClientBootstrapRequest { .. } => {
lcbootstrap_queue.push(work, work_id, &self.log)
}
Work::UnknownBlockAttestation { .. } => {
unknown_block_attestation_queue.push(work)
}
@@ -1735,8 +1769,24 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
/*
* Verification for a chain segment (multiple blocks).
*/
Work::ChainSegment { process_id, blocks } => task_spawner
.spawn_async(async move { worker.process_chain_segment(process_id, blocks).await }),
Work::ChainSegment { process_id, blocks } => {
let notify_execution_layer = if self
.network_globals
.sync_state
.read()
.is_syncing_finalized()
{
NotifyExecutionLayer::No
} else {
NotifyExecutionLayer::Yes
};
task_spawner.spawn_async(async move {
worker
.process_chain_segment(process_id, blocks, notify_execution_layer)
.await
})
}
/*
* Processing of Status Messages.
*/
@@ -1775,7 +1825,6 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
request,
)
}),
Work::BlobsByRangeRequest {
peer_id,
request_id,
@@ -1804,6 +1853,16 @@ impl<T: BeaconChainTypes> BeaconProcessor<T> {
)
}),
/*
* Processing of lightclient bootstrap requests from other peers.
*/
Work::LightClientBootstrapRequest {
peer_id,
request_id,
request,
} => task_spawner.spawn_blocking(move || {
worker.handle_light_client_bootstrap(peer_id, request_id, request)
}),
Work::UnknownBlockAttestation {
message_id,
peer_id,
@@ -8,7 +8,7 @@ use beacon_chain::{
sync_committee_verification::{self, Error as SyncCommitteeError},
validator_monitor::get_block_delay_ms,
BeaconChainError, BeaconChainTypes, BlockError, CountUnrealized, ForkChoiceError,
GossipVerifiedBlock,
GossipVerifiedBlock, NotifyExecutionLayer,
};
use lighthouse_network::{Client, MessageAcceptance, MessageId, PeerAction, PeerId, ReportSource};
use slog::{crit, debug, error, info, trace, warn};
@@ -797,7 +797,7 @@ impl<T: BeaconChainTypes> Worker<T> {
| Err(e @ BlockError::BlockIsAlreadyKnown)
| Err(e @ BlockError::RepeatProposal { .. })
| Err(e @ BlockError::NotFinalizedDescendant { .. }) => {
debug!(self.log, "Could not verify block for gossip, ignoring the block";
debug!(self.log, "Could not verify block for gossip. Ignoring the block";
"error" => %e);
// Prevent recurring behaviour by penalizing the peer slightly.
self.gossip_penalize_peer(
@@ -809,7 +809,7 @@ impl<T: BeaconChainTypes> Worker<T> {
return None;
}
Err(ref e @ BlockError::ExecutionPayloadError(ref epe)) if !epe.penalize_peer() => {
debug!(self.log, "Could not verify block for gossip, ignoring the block";
debug!(self.log, "Could not verify block for gossip. Ignoring the block";
"error" => %e);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore);
return None;
@@ -831,7 +831,7 @@ impl<T: BeaconChainTypes> Worker<T> {
// TODO(merge): reconsider peer scoring for this event.
| Err(e @ BlockError::ParentExecutionPayloadInvalid { .. })
| Err(e @ BlockError::GenesisBlock) => {
warn!(self.log, "Could not verify block for gossip, rejecting the block";
warn!(self.log, "Could not verify block for gossip. Rejecting the block";
"error" => %e);
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(
@@ -939,7 +939,12 @@ impl<T: BeaconChainTypes> Worker<T> {
match self
.chain
.process_block(block_root, verified_block, CountUnrealized::True)
.process_block(
block_root,
verified_block,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await
{
Ok(block_root) => {
@@ -38,7 +38,7 @@ impl<T: BeaconChainTypes> Worker<T> {
/// Creates a log if there is an internal error.
fn send_network_message(&self, message: NetworkMessage<T::EthSpec>) {
self.network_tx.send(message).unwrap_or_else(|e| {
debug!(self.log, "Could not send message to the network service, likely shutdown";
debug!(self.log, "Could not send message to the network service. Likely shutdown";
"error" => %e)
});
}
@@ -15,6 +15,7 @@ use slot_clock::SlotClock;
use ssz_types::VariableList;
use std::sync::Arc;
use task_executor::TaskExecutor;
use types::light_client_bootstrap::LightClientBootstrap;
use types::{Epoch, EthSpec, Hash256, SignedBeaconBlockAndBlobsSidecar, Slot};
use super::Worker;
@@ -308,6 +309,79 @@ impl<T: BeaconChainTypes> Worker<T> {
)
}
/// Handle a `BlocksByRoot` request from the peer.
pub fn handle_light_client_bootstrap(
self,
peer_id: PeerId,
request_id: PeerRequestId,
request: LightClientBootstrapRequest,
) {
let block_root = request.root;
let state_root = match self.chain.get_blinded_block(&block_root) {
Ok(signed_block) => match signed_block {
Some(signed_block) => signed_block.state_root(),
None => {
self.send_error_response(
peer_id,
RPCResponseErrorCode::ResourceUnavailable,
"Bootstrap not avaiable".into(),
request_id,
);
return;
}
},
Err(_) => {
self.send_error_response(
peer_id,
RPCResponseErrorCode::ResourceUnavailable,
"Bootstrap not avaiable".into(),
request_id,
);
return;
}
};
let mut beacon_state = match self.chain.get_state(&state_root, None) {
Ok(beacon_state) => match beacon_state {
Some(state) => state,
None => {
self.send_error_response(
peer_id,
RPCResponseErrorCode::ResourceUnavailable,
"Bootstrap not avaiable".into(),
request_id,
);
return;
}
},
Err(_) => {
self.send_error_response(
peer_id,
RPCResponseErrorCode::ResourceUnavailable,
"Bootstrap not avaiable".into(),
request_id,
);
return;
}
};
let bootstrap = match LightClientBootstrap::from_beacon_state(&mut beacon_state) {
Ok(bootstrap) => bootstrap,
Err(_) => {
self.send_error_response(
peer_id,
RPCResponseErrorCode::ResourceUnavailable,
"Bootstrap not avaiable".into(),
request_id,
);
return;
}
};
self.send_response(
peer_id,
Response::LightClientBootstrap(bootstrap),
request_id,
)
}
/// Handle a `BlocksByRange` request from the peer.
pub fn handle_blocks_by_range_request(
self,
@@ -10,6 +10,7 @@ use crate::sync::{BatchProcessResult, ChainId};
use beacon_chain::CountUnrealized;
use beacon_chain::{
BeaconChainError, BeaconChainTypes, BlockError, ChainSegmentResult, HistoricalBlockError,
NotifyExecutionLayer,
};
use lighthouse_network::PeerAction;
use slog::{debug, error, info, warn};
@@ -89,7 +90,12 @@ impl<T: BeaconChainTypes> Worker<T> {
let slot = block.slot();
let result = self
.chain
.process_block(block_root, block, CountUnrealized::True)
.process_block(
block_root,
block,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
)
.await;
metrics::inc_counter(&metrics::BEACON_PROCESSOR_RPC_BLOCK_IMPORTED_TOTAL);
@@ -131,6 +137,7 @@ impl<T: BeaconChainTypes> Worker<T> {
&self,
sync_type: ChainSegmentProcessId,
downloaded_blocks: Vec<BlockWrapper<T::EthSpec>>,
notify_execution_layer: NotifyExecutionLayer,
) {
let result = match sync_type {
// this a request from the range sync
@@ -140,7 +147,11 @@ impl<T: BeaconChainTypes> Worker<T> {
let sent_blocks = downloaded_blocks.len();
match self
.process_blocks(downloaded_blocks.iter(), count_unrealized)
.process_blocks(
downloaded_blocks.iter(),
count_unrealized,
notify_execution_layer,
)
.await
{
(_, Ok(_)) => {
@@ -230,7 +241,11 @@ impl<T: BeaconChainTypes> Worker<T> {
// parent blocks are ordered from highest slot to lowest, so we need to process in
// reverse
match self
.process_blocks(downloaded_blocks.iter().rev(), CountUnrealized::True)
.process_blocks(
downloaded_blocks.iter().rev(),
CountUnrealized::True,
notify_execution_layer,
)
.await
{
(imported_blocks, Err(e)) => {
@@ -261,11 +276,12 @@ impl<T: BeaconChainTypes> Worker<T> {
&self,
downloaded_blocks: impl Iterator<Item = &'a BlockWrapper<T::EthSpec>>,
count_unrealized: CountUnrealized,
notify_execution_layer: NotifyExecutionLayer,
) -> (usize, Result<(), ChainSegmentFailed>) {
let blocks: Vec<_> = downloaded_blocks.cloned().collect();
match self
.chain
.process_chain_segment(blocks, count_unrealized)
.process_chain_segment(blocks, count_unrealized, notify_execution_layer)
.await
{
ChainSegmentResult::Successful { imported_blocks } => {
@@ -443,7 +459,7 @@ impl<T: BeaconChainTypes> Worker<T> {
} else {
// The block is in the future, but not too far.
debug!(
self.log, "Block is slightly ahead of our slot clock, ignoring.";
self.log, "Block is slightly ahead of our slot clock. Ignoring.";
"present_slot" => present_slot,
"block_slot" => block_slot,
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
+4
View File
@@ -174,6 +174,9 @@ impl<T: BeaconChainTypes> Router<T> {
Request::BlobsByRoot(request) => self
.processor
.on_blobs_by_root_request(peer_id, id, request),
Request::LightClientBootstrap(request) => self
.processor
.on_lightclient_bootstrap(peer_id, id, request),
}
}
@@ -206,6 +209,7 @@ impl<T: BeaconChainTypes> Router<T> {
self.processor
.on_blobs_by_root_response(peer_id, request_id, beacon_blob);
}
Response::LightClientBootstrap(_) => unreachable!(),
}
}
@@ -185,6 +185,18 @@ impl<T: BeaconChainTypes> Processor<T> {
))
}
/// Handle a `LightClientBootstrap` request from the peer.
pub fn on_lightclient_bootstrap(
&mut self,
peer_id: PeerId,
request_id: PeerRequestId,
request: LightClientBootstrapRequest,
) {
self.send_beacon_processor_work(BeaconWorkEvent::lightclient_bootstrap_request(
peer_id, request_id, request,
))
}
/// Handle a `BlocksByRange` request from the peer.
pub fn on_blocks_by_range_request(
&mut self,
@@ -1,4 +1,5 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::time::Duration;
use beacon_chain::{BeaconChainTypes, BlockError};
@@ -16,6 +17,7 @@ use types::signed_block_and_blobs::BlockWrapper;
use crate::beacon_processor::{ChainSegmentProcessId, WorkEvent};
use crate::metrics;
use self::parent_lookup::PARENT_FAIL_TOLERANCE;
use self::{
parent_lookup::{ParentLookup, VerifyError},
single_block_lookup::SingleBlockRequest,
@@ -39,8 +41,11 @@ const FAILED_CHAINS_CACHE_EXPIRY_SECONDS: u64 = 60;
const SINGLE_BLOCK_LOOKUP_MAX_ATTEMPTS: u8 = 3;
pub(crate) struct BlockLookups<T: BeaconChainTypes> {
/// A collection of parent block lookups.
parent_queue: SmallVec<[ParentLookup<T>; 3]>,
/// Parent chain lookups being downloaded.
parent_lookups: SmallVec<[ParentLookup<T>; 3]>,
processing_parent_lookups:
HashMap<Hash256, (Vec<Hash256>, SingleBlockRequest<PARENT_FAIL_TOLERANCE>)>,
/// A cache of failed chain lookups to prevent duplicate searches.
failed_chains: LRUTimeCache<Hash256>,
@@ -58,7 +63,8 @@ pub(crate) struct BlockLookups<T: BeaconChainTypes> {
impl<T: BeaconChainTypes> BlockLookups<T> {
pub fn new(log: Logger) -> Self {
Self {
parent_queue: Default::default(),
parent_lookups: Default::default(),
processing_parent_lookups: Default::default(),
failed_chains: LRUTimeCache::new(Duration::from_secs(
FAILED_CHAINS_CACHE_EXPIRY_SECONDS,
)),
@@ -81,6 +87,23 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
return;
}
if self.parent_lookups.iter_mut().any(|parent_req| {
parent_req.add_peer(&hash, &peer_id) || parent_req.contains_block(&hash)
}) {
// If the block was already downloaded, or is being downloaded in this moment, do not
// request it.
return;
}
if self
.processing_parent_lookups
.values()
.any(|(hashes, _last_parent_request)| hashes.contains(&hash))
{
// we are already processing this block, ignore it.
return;
}
debug!(
self.log,
"Searching for block";
@@ -123,8 +146,8 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
// Make sure this block is not already downloaded, and that neither it or its parent is
// being searched for.
if self.parent_queue.iter_mut().any(|parent_req| {
parent_req.contains_block(block.block())
if self.parent_lookups.iter_mut().any(|parent_req| {
parent_req.contains_block(&block_root)
|| parent_req.add_peer(&block_root, &peer_id)
|| parent_req.add_peer(&parent_root, &peer_id)
}) {
@@ -132,6 +155,15 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
return;
}
if self
.processing_parent_lookups
.values()
.any(|(hashes, _peers)| hashes.contains(&block_root) || hashes.contains(&parent_root))
{
// we are already processing this block, ignore it.
return;
}
let parent_lookup = ParentLookup::new(block_root, block, peer_id);
self.request_parent(parent_lookup, cx);
}
@@ -212,11 +244,11 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
cx: &mut SyncNetworkContext<T>,
) {
let mut parent_lookup = if let Some(pos) = self
.parent_queue
.parent_lookups
.iter()
.position(|request| request.pending_response(id))
{
self.parent_queue.remove(pos)
self.parent_lookups.remove(pos)
} else {
if block.is_some() {
debug!(self.log, "Response for a parent lookup request that was not found"; "peer_id" => %peer_id);
@@ -238,13 +270,13 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
)
.is_ok()
{
self.parent_queue.push(parent_lookup)
self.parent_lookups.push(parent_lookup)
}
}
Ok(None) => {
// Request finished successfully, nothing else to do. It will be removed after the
// processing result arrives.
self.parent_queue.push(parent_lookup);
self.parent_lookups.push(parent_lookup);
}
Err(e) => match e {
VerifyError::RootMismatch
@@ -281,7 +313,7 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
metrics::set_gauge(
&metrics::SYNC_PARENT_BLOCK_LOOKUPS,
self.parent_queue.len() as i64,
self.parent_lookups.len() as i64,
);
}
@@ -329,11 +361,11 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
/* Check disconnection for parent lookups */
while let Some(pos) = self
.parent_queue
.parent_lookups
.iter_mut()
.position(|req| req.check_peer_disconnected(peer_id).is_err())
{
let parent_lookup = self.parent_queue.remove(pos);
let parent_lookup = self.parent_lookups.remove(pos);
trace!(self.log, "Parent lookup's peer disconnected"; &parent_lookup);
self.request_parent(parent_lookup, cx);
}
@@ -347,11 +379,11 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
cx: &mut SyncNetworkContext<T>,
) {
if let Some(pos) = self
.parent_queue
.parent_lookups
.iter()
.position(|request| request.pending_response(id))
{
let mut parent_lookup = self.parent_queue.remove(pos);
let mut parent_lookup = self.parent_lookups.remove(pos);
parent_lookup.download_failed();
trace!(self.log, "Parent lookup request failed"; &parent_lookup);
self.request_parent(parent_lookup, cx);
@@ -360,7 +392,7 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
};
metrics::set_gauge(
&metrics::SYNC_PARENT_BLOCK_LOOKUPS,
self.parent_queue.len() as i64,
self.parent_lookups.len() as i64,
);
}
@@ -475,7 +507,7 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
cx: &mut SyncNetworkContext<T>,
) {
let (mut parent_lookup, peer_id) = if let Some((pos, peer)) = self
.parent_queue
.parent_lookups
.iter()
.enumerate()
.find_map(|(pos, request)| {
@@ -483,7 +515,7 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
.get_processing_peer(chain_hash)
.map(|peer| (pos, peer))
}) {
(self.parent_queue.remove(pos), peer)
(self.parent_lookups.remove(pos), peer)
} else {
return debug!(self.log, "Process response for a parent lookup request that was not found"; "chain_hash" => %chain_hash);
};
@@ -525,15 +557,15 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
);
}
};
let chain_hash = parent_lookup.chain_hash();
let blocks = parent_lookup.chain_blocks();
let (chain_hash, blocks, hashes, request) = parent_lookup.parts_for_processing();
let process_id = ChainSegmentProcessId::ParentLookup(chain_hash);
let work = WorkEvent::chain_segment(process_id, blocks);
match beacon_processor_send.try_send(work) {
Ok(_) => {
self.parent_queue.push(parent_lookup);
self.processing_parent_lookups
.insert(chain_hash, (hashes, request));
}
Err(e) => {
error!(
@@ -587,7 +619,7 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
metrics::set_gauge(
&metrics::SYNC_PARENT_BLOCK_LOOKUPS,
self.parent_queue.len() as i64,
self.parent_lookups.len() as i64,
);
}
@@ -597,14 +629,11 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
result: BatchProcessResult,
cx: &mut SyncNetworkContext<T>,
) {
let parent_lookup = if let Some(pos) = self
.parent_queue
.iter()
.position(|request| request.chain_hash() == chain_hash)
{
self.parent_queue.remove(pos)
} else {
return debug!(self.log, "Chain process response for a parent lookup request that was not found"; "chain_hash" => %chain_hash);
let request = match self.processing_parent_lookups.remove(&chain_hash) {
Some((_hashes, request)) => request,
None => {
return debug!(self.log, "Chain process response for a parent lookup request that was not found"; "chain_hash" => %chain_hash, "result" => ?result)
}
};
debug!(self.log, "Parent chain processed"; "chain_hash" => %chain_hash, "result" => ?result);
@@ -616,8 +645,8 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
imported_blocks: _,
penalty,
} => {
self.failed_chains.insert(parent_lookup.chain_hash());
for &peer_id in parent_lookup.used_peers() {
self.failed_chains.insert(chain_hash);
for peer_id in request.used_peers {
cx.report_peer(peer_id, penalty, "parent_chain_failure")
}
}
@@ -628,7 +657,7 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
metrics::set_gauge(
&metrics::SYNC_PARENT_BLOCK_LOOKUPS,
self.parent_queue.len() as i64,
self.parent_lookups.len() as i64,
);
}
@@ -704,14 +733,14 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
}
Ok(_) => {
debug!(self.log, "Requesting parent"; &parent_lookup);
self.parent_queue.push(parent_lookup)
self.parent_lookups.push(parent_lookup)
}
}
// We remove and add back again requests so we want this updated regardless of outcome.
metrics::set_gauge(
&metrics::SYNC_PARENT_BLOCK_LOOKUPS,
self.parent_queue.len() as i64,
self.parent_lookups.len() as i64,
);
}
@@ -722,6 +751,6 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
/// Drops all the parent chain requests and returns how many requests were dropped.
pub fn drop_parent_chain_requests(&mut self) -> usize {
self.parent_queue.drain(..).len()
self.parent_lookups.drain(..).len()
}
}
@@ -25,7 +25,7 @@ pub(crate) struct ParentLookup<T: BeaconChainTypes> {
/// The root of the block triggering this parent request.
chain_hash: Hash256,
/// The blocks that have currently been downloaded.
downloaded_blocks: Vec<BlockWrapper<T::EthSpec>>,
downloaded_blocks: Vec<RootBlockTuple<T::EthSpec>>,
/// Request of the last parent.
current_parent_request: SingleBlockRequest<PARENT_FAIL_TOLERANCE>,
/// Id of the last parent request.
@@ -54,10 +54,10 @@ pub enum RequestError {
}
impl<T: BeaconChainTypes> ParentLookup<T> {
pub fn contains_block(&self, block: &SignedBeaconBlock<T::EthSpec>) -> bool {
pub fn contains_block(&self, block_root: &Hash256) -> bool {
self.downloaded_blocks
.iter()
.any(|d_block| d_block.block() == block)
.any(|(root, _d_block)| root == block_root)
}
pub fn new(block_root: Hash256, block: BlockWrapper<T::EthSpec>, peer_id: PeerId) -> Self {
@@ -65,7 +65,7 @@ impl<T: BeaconChainTypes> ParentLookup<T> {
Self {
chain_hash: block_root,
downloaded_blocks: vec![block],
downloaded_blocks: vec![(block_root, block)],
current_parent_request,
current_parent_request_id: None,
}
@@ -97,7 +97,8 @@ impl<T: BeaconChainTypes> ParentLookup<T> {
pub fn add_block(&mut self, block: BlockWrapper<T::EthSpec>) {
let next_parent = block.parent_root();
self.downloaded_blocks.push(block);
let current_root = self.current_parent_request.hash;
self.downloaded_blocks.push((current_root, block));
self.current_parent_request.hash = next_parent;
self.current_parent_request.state = single_block_lookup::State::AwaitingDownload;
self.current_parent_request_id = None;
@@ -107,6 +108,32 @@ impl<T: BeaconChainTypes> ParentLookup<T> {
self.current_parent_request_id == Some(req_id)
}
/// Consumes the parent request and destructures it into it's parts.
#[allow(clippy::type_complexity)]
pub fn parts_for_processing(
self,
) -> (
Hash256,
Vec<BlockWrapper<T::EthSpec>>,
Vec<Hash256>,
SingleBlockRequest<PARENT_FAIL_TOLERANCE>,
) {
let ParentLookup {
chain_hash,
downloaded_blocks,
current_parent_request,
current_parent_request_id: _,
} = self;
let block_count = downloaded_blocks.len();
let mut blocks = Vec::with_capacity(block_count);
let mut hashes = Vec::with_capacity(block_count);
for (hash, block) in downloaded_blocks {
blocks.push(block);
hashes.push(hash);
}
(chain_hash, blocks, hashes, current_parent_request)
}
/// Get the parent lookup's chain hash.
pub fn chain_hash(&self) -> Hash256 {
self.chain_hash
@@ -122,10 +149,6 @@ impl<T: BeaconChainTypes> ParentLookup<T> {
self.current_parent_request_id = None;
}
pub fn chain_blocks(&mut self) -> Vec<BlockWrapper<T::EthSpec>> {
std::mem::take(&mut self.downloaded_blocks)
}
/// Verifies that the received block is what we requested. If so, parent lookup now waits for
/// the processing result of the block.
pub fn verify_block(
@@ -255,7 +255,7 @@ fn test_single_block_lookup_becomes_parent_request() {
assert_eq!(bl.single_block_lookups.len(), 0);
rig.expect_parent_request();
rig.expect_empty_network();
assert_eq!(bl.parent_queue.len(), 1);
assert_eq!(bl.parent_lookups.len(), 1);
}
#[test]
@@ -283,7 +283,7 @@ fn test_parent_lookup_happy_path() {
was_non_empty: true,
};
bl.parent_chain_processed(chain_hash, process_result, &mut cx);
assert_eq!(bl.parent_queue.len(), 0);
assert_eq!(bl.parent_lookups.len(), 0);
}
#[test]
@@ -320,7 +320,7 @@ fn test_parent_lookup_wrong_response() {
was_non_empty: true,
};
bl.parent_chain_processed(chain_hash, process_result, &mut cx);
assert_eq!(bl.parent_queue.len(), 0);
assert_eq!(bl.parent_lookups.len(), 0);
}
#[test]
@@ -352,7 +352,7 @@ fn test_parent_lookup_empty_response() {
was_non_empty: true,
};
bl.parent_chain_processed(chain_hash, process_result, &mut cx);
assert_eq!(bl.parent_queue.len(), 0);
assert_eq!(bl.parent_lookups.len(), 0);
}
#[test]
@@ -383,7 +383,7 @@ fn test_parent_lookup_rpc_failure() {
was_non_empty: true,
};
bl.parent_chain_processed(chain_hash, process_result, &mut cx);
assert_eq!(bl.parent_queue.len(), 0);
assert_eq!(bl.parent_lookups.len(), 0);
}
#[test]
@@ -415,11 +415,11 @@ fn test_parent_lookup_too_many_attempts() {
}
}
if i < parent_lookup::PARENT_FAIL_TOLERANCE {
assert_eq!(bl.parent_queue[0].failed_attempts(), dbg!(i));
assert_eq!(bl.parent_lookups[0].failed_attempts(), dbg!(i));
}
}
assert_eq!(bl.parent_queue.len(), 0);
assert_eq!(bl.parent_lookups.len(), 0);
}
#[test]
@@ -446,11 +446,11 @@ fn test_parent_lookup_too_many_download_attempts_no_blacklist() {
rig.expect_penalty();
}
if i < parent_lookup::PARENT_FAIL_TOLERANCE {
assert_eq!(bl.parent_queue[0].failed_attempts(), dbg!(i));
assert_eq!(bl.parent_lookups[0].failed_attempts(), dbg!(i));
}
}
assert_eq!(bl.parent_queue.len(), 0);
assert_eq!(bl.parent_lookups.len(), 0);
assert!(!bl.failed_chains.contains(&block_hash));
assert!(!bl.failed_chains.contains(&parent.canonical_root()));
}
@@ -487,7 +487,7 @@ fn test_parent_lookup_too_many_processing_attempts_must_blacklist() {
}
assert!(bl.failed_chains.contains(&block_hash));
assert_eq!(bl.parent_queue.len(), 0);
assert_eq!(bl.parent_lookups.len(), 0);
}
#[test]
@@ -541,7 +541,7 @@ fn test_parent_lookup_disconnection() {
&mut cx,
);
bl.peer_disconnected(&peer_id, &mut cx);
assert!(bl.parent_queue.is_empty());
assert!(bl.parent_lookups.is_empty());
}
#[test]
@@ -594,5 +594,78 @@ fn test_parent_lookup_ignored_response() {
// Return an Ignored result. The request should be dropped
bl.parent_block_processed(chain_hash, BlockProcessResult::Ignored, &mut cx);
rig.expect_empty_network();
assert_eq!(bl.parent_queue.len(), 0);
assert_eq!(bl.parent_lookups.len(), 0);
}
/// This is a regression test.
#[test]
fn test_same_chain_race_condition() {
let (mut bl, mut cx, mut rig) = TestRig::test_setup(Some(Level::Debug));
#[track_caller]
fn parent_lookups_consistency(bl: &BlockLookups<T>) {
let hashes: Vec<_> = bl
.parent_lookups
.iter()
.map(|req| req.chain_hash())
.collect();
let expected = hashes.len();
assert_eq!(
expected,
hashes
.into_iter()
.collect::<std::collections::HashSet<_>>()
.len(),
"duplicated chain hashes in parent queue"
)
}
// if we use one or two blocks it will match on the hash or the parent hash, so make a longer
// chain.
let depth = 4;
let mut blocks = Vec::<Arc<SignedBeaconBlock<E>>>::with_capacity(depth);
while blocks.len() < depth {
let parent = blocks
.last()
.map(|b| b.canonical_root())
.unwrap_or_else(Hash256::random);
let block = Arc::new(rig.block_with_parent(parent));
blocks.push(block);
}
let peer_id = PeerId::random();
let trigger_block = blocks.pop().unwrap();
let chain_hash = trigger_block.canonical_root();
bl.search_parent(chain_hash, trigger_block.clone(), peer_id, &mut cx);
for (i, block) in blocks.into_iter().rev().enumerate() {
let id = rig.expect_parent_request();
// the block
bl.parent_lookup_response(id, peer_id, Some(block.clone()), D, &mut cx);
// the stream termination
bl.parent_lookup_response(id, peer_id, None, D, &mut cx);
// the processing request
rig.expect_block_process();
// the processing result
if i + 2 == depth {
// one block was removed
bl.parent_block_processed(chain_hash, BlockError::BlockIsAlreadyKnown.into(), &mut cx)
} else {
bl.parent_block_processed(chain_hash, BlockError::ParentUnknown(block).into(), &mut cx)
}
parent_lookups_consistency(&bl)
}
// Processing succeeds, now the rest of the chain should be sent for processing.
rig.expect_parent_chain_process();
// Try to get this block again while the chain is being processed. We should not request it again.
let peer_id = PeerId::random();
bl.search_parent(chain_hash, trigger_block, peer_id, &mut cx);
parent_lookups_consistency(&bl);
let process_result = BatchProcessResult::Success {
was_non_empty: true,
};
bl.parent_chain_processed(chain_hash, process_result, &mut cx);
assert_eq!(bl.parent_lookups.len(), 0);
}
+1 -1
View File
@@ -710,7 +710,7 @@ impl<T: BeaconChainTypes> SyncManager<T> {
// Some logs.
if dropped_single_blocks_requests > 0 || dropped_parent_chain_requests > 0 {
debug!(self.log, "Execution engine not online, dropping active requests.";
debug!(self.log, "Execution engine not online. Dropping active requests.";
"dropped_single_blocks_requests" => dropped_single_blocks_requests,
"dropped_parent_chain_requests" => dropped_parent_chain_requests,
);
@@ -525,7 +525,7 @@ impl<T: BeaconChainTypes> SyncNetworkContext<T> {
source: ReportSource::SyncService,
})
.unwrap_or_else(|_| {
warn!(self.log, "Could not report peer, channel failed");
warn!(self.log, "Could not report peer: channel failed");
});
}
@@ -540,7 +540,7 @@ impl<T: BeaconChainTypes> SyncNetworkContext<T> {
msg,
})
.unwrap_or_else(|e| {
warn!(self.log, "Could not report peer, channel failed"; "error"=> %e);
warn!(self.log, "Could not report peer: channel failed"; "error"=> %e);
});
}
+23
View File
@@ -725,6 +725,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.takes_value(true)
.conflicts_with("checkpoint-state")
)
.arg(
Arg::with_name("checkpoint-sync-url-timeout")
.long("checkpoint-sync-url-timeout")
.help("Set the timeout for checkpoint sync calls to remote beacon node HTTP endpoint.")
.value_name("SECONDS")
.takes_value(true)
.default_value("60")
)
.arg(
Arg::with_name("reconstruct-historic-states")
.long("reconstruct-historic-states")
@@ -871,4 +879,19 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
Useful if you intend to run a non-validating beacon node.")
.takes_value(false)
)
.arg(
Arg::with_name("light-client-server")
.long("light-client-server")
.help("Act as a full node supporting light clients on the p2p network \
[experimental]")
.takes_value(false)
)
.arg(
Arg::with_name("gui")
.long("gui")
.hidden(true)
.help("Enable the graphical user interface and all its requirements. \
This is equivalent to --http and --validator-monitor-auto.")
.takes_value(false)
)
}
+31 -14
View File
@@ -14,6 +14,7 @@ use std::cmp::max;
use std::fmt::Debug;
use std::fmt::Write;
use std::fs;
use std::net::Ipv6Addr;
use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs};
use std::path::{Path, PathBuf};
use std::str::FromStr;
@@ -34,13 +35,13 @@ pub fn get_config<E: EthSpec>(
let spec = &context.eth2_config.spec;
let log = context.log();
let mut client_config = ClientConfig {
data_dir: get_data_dir(cli_args),
..Default::default()
};
let mut client_config = ClientConfig::default();
// Update the client's data directory
client_config.set_data_dir(get_data_dir(cli_args));
// If necessary, remove any existing database and configuration
if client_config.data_dir.exists() && cli_args.is_present("purge-db") {
if client_config.data_dir().exists() && cli_args.is_present("purge-db") {
// Remove the chain_db.
let chain_db = client_config.get_db_path();
if chain_db.exists() {
@@ -57,11 +58,11 @@ pub fn get_config<E: EthSpec>(
}
// Create `datadir` and any non-existing parent directories.
fs::create_dir_all(&client_config.data_dir)
fs::create_dir_all(client_config.data_dir())
.map_err(|e| format!("Failed to create data dir: {}", e))?;
// logs the chosen data directory
let mut log_dir = client_config.data_dir.clone();
let mut log_dir = client_config.data_dir().clone();
// remove /beacon from the end
log_dir.pop();
info!(log, "Data directory initialised"; "datadir" => log_dir.into_os_string().into_string().expect("Datadir should be a valid os string"));
@@ -69,10 +70,13 @@ pub fn get_config<E: EthSpec>(
/*
* Networking
*/
let data_dir_ref = client_config.data_dir().clone();
set_network_config(
&mut client_config.network,
cli_args,
&client_config.data_dir,
&data_dir_ref,
log,
false,
)?;
@@ -303,7 +307,7 @@ pub fn get_config<E: EthSpec>(
} else if let Some(jwt_secret_key) = cli_args.value_of("execution-jwt-secret-key") {
use std::fs::File;
use std::io::Write;
secret_file = client_config.data_dir.join(DEFAULT_JWT_FILE);
secret_file = client_config.data_dir().join(DEFAULT_JWT_FILE);
let mut jwt_secret_key_file = File::create(secret_file.clone())
.map_err(|e| format!("Error while creating jwt_secret_key file: {:?}", e))?;
jwt_secret_key_file
@@ -332,7 +336,7 @@ pub fn get_config<E: EthSpec>(
clap_utils::parse_optional(cli_args, "suggested-fee-recipient")?;
el_config.jwt_id = clap_utils::parse_optional(cli_args, "execution-jwt-id")?;
el_config.jwt_version = clap_utils::parse_optional(cli_args, "execution-jwt-version")?;
el_config.default_datadir = client_config.data_dir.clone();
el_config.default_datadir = client_config.data_dir().clone();
el_config.builder_profit_threshold =
clap_utils::parse_required(cli_args, "builder-profit-threshold")?;
let execution_timeout_multiplier =
@@ -446,6 +450,8 @@ pub fn get_config<E: EthSpec>(
.extend_from_slice(boot_nodes)
}
}
client_config.chain.checkpoint_sync_url_timeout =
clap_utils::parse_required::<u64>(cli_args, "checkpoint-sync-url-timeout")?;
client_config.genesis = if let Some(genesis_state_bytes) =
eth2_network_config.genesis_state_bytes.clone()
@@ -576,7 +582,7 @@ pub fn get_config<E: EthSpec>(
let slasher_dir = if let Some(slasher_dir) = cli_args.value_of("slasher-dir") {
PathBuf::from(slasher_dir)
} else {
client_config.data_dir.join("slasher_db")
client_config.data_dir().join("slasher_db")
};
let mut slasher_config = slasher::Config::new(slasher_dir);
@@ -708,6 +714,12 @@ pub fn get_config<E: EthSpec>(
client_config.chain.builder_fallback_disable_checks =
cli_args.is_present("builder-fallback-disable-checks");
// Graphical user interface config.
if cli_args.is_present("gui") {
client_config.http_api.enabled = true;
client_config.validator_monitor_auto = true;
}
Ok(client_config)
}
@@ -837,9 +849,11 @@ pub fn set_network_config(
}
if cli_args.is_present("enr-match") {
// set the enr address to localhost if the address is 0.0.0.0
if config.listen_address == "0.0.0.0".parse::<IpAddr>().expect("valid ip addr") {
config.enr_address = Some("127.0.0.1".parse::<IpAddr>().expect("valid ip addr"));
// set the enr address to localhost if the address is unspecified
if config.listen_address == IpAddr::V4(Ipv4Addr::UNSPECIFIED) {
config.enr_address = Some(IpAddr::V4(Ipv4Addr::LOCALHOST));
} else if config.listen_address == IpAddr::V6(Ipv6Addr::UNSPECIFIED) {
config.enr_address = Some(IpAddr::V6(Ipv6Addr::LOCALHOST));
} else {
config.enr_address = Some(config.listen_address);
}
@@ -919,6 +933,9 @@ pub fn set_network_config(
config.discv5_config.table_filter = |_| true;
}
// Light client server config.
config.enable_light_client_server = cli_args.is_present("light-client-server");
Ok(())
}
+8 -1
View File
@@ -3,7 +3,7 @@ use crate::config::StoreConfigError;
use crate::hot_cold_store::HotColdDBError;
use ssz::DecodeError;
use state_processing::BlockReplayError;
use types::{BeaconStateError, Hash256, Slot};
use types::{BeaconStateError, Hash256, InconsistentFork, Slot};
pub type Result<T> = std::result::Result<T, Error>;
@@ -47,6 +47,7 @@ pub enum Error {
ResyncRequiredForExecutionPayloadSeparation,
SlotClockUnavailableForMigration,
V9MigrationFailure(Hash256),
InconsistentFork(InconsistentFork),
}
pub trait HandleUnavailable<T> {
@@ -105,6 +106,12 @@ impl From<BlockReplayError> for Error {
}
}
impl From<InconsistentFork> for Error {
fn from(e: InconsistentFork) -> Error {
Error::InconsistentFork(e)
}
}
#[derive(Debug)]
pub struct DBError {
pub message: String,
+21 -2
View File
@@ -362,7 +362,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
} else if !self.config.prune_payloads {
// If payload pruning is disabled there's a chance we may have the payload of
// this finalized block. Attempt to load it but don't error in case it's missing.
if let Some(payload) = self.get_execution_payload(block_root)? {
let fork_name = blinded_block.fork_name(&self.spec)?;
if let Some(payload) = self.get_execution_payload(block_root, fork_name)? {
DatabaseBlock::Full(
blinded_block
.try_into_full_block(Some(payload))
@@ -411,8 +412,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
blinded_block: SignedBeaconBlock<E, BlindedPayload<E>>,
) -> Result<SignedBeaconBlock<E>, Error> {
if blinded_block.message().execution_payload().is_ok() {
let fork_name = blinded_block.fork_name(&self.spec)?;
let execution_payload = self
.get_execution_payload(block_root)?
.get_execution_payload(block_root, fork_name)?
.ok_or(HotColdDBError::MissingExecutionPayload(*block_root))?;
blinded_block.try_into_full_block(Some(execution_payload))
} else {
@@ -455,9 +457,26 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
}
/// Load the execution payload for a block from disk.
/// This method deserializes with the proper fork.
pub fn get_execution_payload(
&self,
block_root: &Hash256,
fork_name: ForkName,
) -> Result<Option<ExecutionPayload<E>>, Error> {
let column = ExecutionPayload::<E>::db_column().into();
let key = block_root.as_bytes();
match self.hot_db.get_bytes(column, key)? {
Some(bytes) => Ok(Some(ExecutionPayload::from_ssz_bytes(&bytes, fork_name)?)),
None => Ok(None),
}
}
/// Load the execution payload for a block from disk.
/// DANGEROUS: this method just guesses the fork.
pub fn get_execution_payload_dangerous_fork_agnostic(
&self,
block_root: &Hash256,
) -> Result<Option<ExecutionPayload<E>>, Error> {
self.get_item(block_root)
}