resolve merge conflicts

This commit is contained in:
Eitan Seri-Levi
2026-04-30 01:51:26 +02:00
544 changed files with 48684 additions and 18351 deletions

View File

@@ -12,8 +12,8 @@ use crate::{
ExecutionPayloadError,
};
use execution_layer::{
BlockProposalContents, BlockProposalContentsType, BuilderParams, NewPayloadRequest,
PayloadAttributes, PayloadParameters, PayloadStatus,
BlockProposalContentsType, BuilderParams, NewPayloadRequest, PayloadAttributes,
PayloadParameters, PayloadStatus,
};
use fork_choice::{InvalidationOperation, PayloadVerificationStatus};
use proto_array::{Block as ProtoBlock, ExecutionStatus};
@@ -21,24 +21,18 @@ use slot_clock::SlotClock;
use ssz_types::VariableList;
use state_processing::per_block_processing::{
compute_timestamp_at_slot, get_expected_withdrawals, is_execution_enabled,
is_merge_transition_complete, partially_verify_execution_payload,
partially_verify_execution_payload,
};
use std::sync::Arc;
use tokio::task::JoinHandle;
use tracing::{Instrument, debug, debug_span, info, warn};
use tree_hash::TreeHash;
use types::payload::BlockProductionVersion;
use types::execution::BlockProductionVersion;
use types::*;
pub type PreparePayloadResult<E> = Result<BlockProposalContentsType<E>, BlockProductionError>;
pub type PreparePayloadHandle<E> = JoinHandle<Option<PreparePayloadResult<E>>>;
#[derive(PartialEq)]
pub enum AllowOptimisticImport {
Yes,
No,
}
/// Signal whether the execution payloads of new blocks should be
/// immediately verified with the EL or imported optimistically without
/// any EL communication.
@@ -64,7 +58,10 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
state: &BeaconState<T::EthSpec>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<Self, BlockError> {
let payload_verification_status = if is_execution_enabled(state, block.message().body()) {
let payload_verification_status = if block.fork_name_unchecked().gloas_enabled() {
// Gloas blocks don't contain an execution payload.
Some(PayloadVerificationStatus::Irrelevant)
} else 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
@@ -152,7 +149,7 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
}
}
/// Verify that `execution_payload` contained by `block` is considered valid by an execution
/// Verify that `execution_payload` is considered valid by an execution
/// engine.
///
/// ## Specification
@@ -161,18 +158,18 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
/// contains a few extra checks by running `partially_verify_execution_payload` first:
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.9/specs/bellatrix/beacon-chain.md#notify_new_payload
async fn notify_new_payload<T: BeaconChainTypes>(
pub async fn notify_new_payload<T: BeaconChainTypes>(
chain: &Arc<BeaconChain<T>>,
block: BeaconBlockRef<'_, T::EthSpec>,
il_transactions: Transactions<T::EthSpec>,
) -> Result<PayloadVerificationStatus, BlockError> {
let slot = block.slot();
let parent_beacon_block_root = block.parent_root();
let execution_layer = chain
.execution_layer
.as_ref()
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
let execution_block_hash = block.execution_payload()?.block_hash();
// TODO(eip-7805) we can remove this later
if !il_transactions.is_empty() {
info!(
@@ -181,11 +178,11 @@ async fn notify_new_payload<T: BeaconChainTypes>(
);
}
let new_payload_request =
NewPayloadRequest::try_from_block_and_il_transactions(block, il_transactions)?;
let execution_block_hash = new_payload_request.execution_payload_ref().block_hash();
let new_payload_response = execution_layer
.notify_new_payload(NewPayloadRequest::try_from_block_and_il_transactions(
block,
il_transactions,
)?)
.notify_new_payload(new_payload_request.clone())
.await;
match new_payload_response {
@@ -202,10 +199,7 @@ async fn notify_new_payload<T: BeaconChainTypes>(
?validation_error,
?latest_valid_hash,
?execution_block_hash,
root = ?block.tree_hash_root(),
graffiti = block.body().graffiti().as_utf8_lossy(),
proposer_index = block.proposer_index(),
slot = %block.slot(),
%slot,
method = "new_payload",
"Invalid execution payload"
);
@@ -228,9 +222,7 @@ async fn notify_new_payload<T: BeaconChainTypes>(
{
// This block has not yet been applied to fork choice, so the latest block that was
// imported to fork choice was the parent.
let latest_root = block.parent_root();
// If the payload is invalid because it didn't satisfy the inclusion lit
// If the payload is invalid because it didn't satisfy the inclusion list
// transactions for this slot, update the fork choice store before processing
// the invalid EL payload.
if *validation_error == Some("INVALID_INCLUSION_LIST".to_string()) {
@@ -249,7 +241,7 @@ async fn notify_new_payload<T: BeaconChainTypes>(
chain
.process_invalid_execution_payload(&InvalidationOperation::InvalidateMany {
head_block_root: latest_root,
head_block_root: parent_beacon_block_root,
always_invalidate_head: false,
latest_valid_ancestor: latest_valid_hash,
})
@@ -264,10 +256,7 @@ async fn notify_new_payload<T: BeaconChainTypes>(
warn!(
?validation_error,
?execution_block_hash,
root = ?block.tree_hash_root(),
graffiti = block.body().graffiti().as_utf8_lossy(),
proposer_index = block.proposer_index(),
slot = %block.slot(),
%slot,
method = "new_payload",
"Invalid execution payload block hash"
);
@@ -282,75 +271,71 @@ async fn notify_new_payload<T: BeaconChainTypes>(
}
}
/// Verify that the block which triggers the merge is valid to be imported to fork choice.
///
/// ## Errors
///
/// Will return an error when using a pre-merge fork `state`. Ensure to only run this function
/// after the merge fork.
///
/// ## Specification
///
/// Equivalent to the `validate_merge_block` function in the merge Fork Choice Changes:
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/fork-choice.md#validate_merge_block
pub async fn validate_merge_block<T: BeaconChainTypes>(
/// Notify the EL with a pre-built `NewPayloadRequest`. Used for Gloas where the payload
/// comes from a `SignedExecutionPayloadEnvelope` rather than the beacon block body.
pub async fn notify_new_payload_with_request<T: BeaconChainTypes>(
chain: &Arc<BeaconChain<T>>,
block: BeaconBlockRef<'_, T::EthSpec>,
allow_optimistic_import: AllowOptimisticImport,
) -> Result<(), BlockError> {
let spec = &chain.spec;
let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
let execution_payload = block.execution_payload()?;
if spec.terminal_block_hash != ExecutionBlockHash::zero() {
if block_epoch < spec.terminal_block_hash_activation_epoch {
return Err(ExecutionPayloadError::InvalidActivationEpoch {
activation_epoch: spec.terminal_block_hash_activation_epoch,
epoch: block_epoch,
}
.into());
}
if execution_payload.parent_hash() != spec.terminal_block_hash {
return Err(ExecutionPayloadError::InvalidTerminalBlockHash {
terminal_block_hash: spec.terminal_block_hash,
payload_parent_hash: execution_payload.parent_hash(),
}
.into());
}
return Ok(());
}
slot: Slot,
parent_beacon_block_root: Hash256,
new_payload_request: NewPayloadRequest<'_, T::EthSpec>,
) -> Result<PayloadVerificationStatus, BlockError> {
let execution_layer = chain
.execution_layer
.as_ref()
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
let is_valid_terminal_pow_block = execution_layer
.is_valid_terminal_pow_block_hash(execution_payload.parent_hash(), spec)
.await
.map_err(ExecutionPayloadError::from)?;
let execution_block_hash = new_payload_request.execution_payload_ref().block_hash();
let new_payload_response = execution_layer
.notify_new_payload(new_payload_request.clone())
.await;
match is_valid_terminal_pow_block {
Some(true) => Ok(()),
Some(false) => Err(ExecutionPayloadError::InvalidTerminalPoWBlock {
parent_hash: execution_payload.parent_hash(),
}
.into()),
None => {
if allow_optimistic_import == AllowOptimisticImport::Yes {
debug!(
block_hash = ?execution_payload.parent_hash(),
msg = "the terminal block/parent was unavailable",
"Optimistically importing merge transition block"
);
Ok(())
} else {
Err(ExecutionPayloadError::UnverifiedNonOptimisticCandidate.into())
match new_payload_response {
Ok(status) => match status {
PayloadStatus::Valid => Ok(PayloadVerificationStatus::Verified),
PayloadStatus::Syncing | PayloadStatus::Accepted => {
Ok(PayloadVerificationStatus::Optimistic)
}
}
PayloadStatus::Invalid {
latest_valid_hash,
ref validation_error,
} => {
warn!(
?validation_error,
?latest_valid_hash,
?execution_block_hash,
%slot,
method = "new_payload",
"Invalid execution payload"
);
if let Some(latest_valid_hash) =
latest_valid_hash.filter(|hash| *hash != ExecutionBlockHash::zero())
{
chain
.process_invalid_execution_payload(&InvalidationOperation::InvalidateMany {
head_block_root: parent_beacon_block_root,
always_invalidate_head: false,
latest_valid_ancestor: latest_valid_hash,
})
.await?;
}
Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into())
}
PayloadStatus::InvalidBlockHash {
ref validation_error,
} => {
warn!(
?validation_error,
?execution_block_hash,
%slot,
method = "new_payload",
"Invalid execution payload block hash"
);
Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into())
}
},
Err(e) => Err(ExecutionPayloadError::RequestFailed(e).into()),
}
}
@@ -361,16 +346,22 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
block: BeaconBlockRef<'_, T::EthSpec>,
chain: &BeaconChain<T>,
) -> Result<(), BlockError> {
// Gloas blocks don't have an execution payload in the block body.
// Bid-related validations are handled in gossip block verification.
if block.fork_name_unchecked().gloas_enabled() {
return Ok(());
}
// Only apply this validation if this is a Bellatrix beacon block.
if let Ok(execution_payload) = block.body().execution_payload() {
// This logic should match `is_execution_enabled`. We use only the execution block hash of
// the parent here in order to avoid loading the parent state during gossip verification.
// Check parent execution status to determine if we should validate the payload.
// We use only the execution status of the parent here to avoid loading the parent state
// during gossip verification.
let is_merge_transition_complete = match parent_block.execution_status {
// Optimistically declare that an "unknown" status block has completed the merge.
let parent_has_execution = match parent_block.execution_status {
// Parent has valid or optimistic execution status.
ExecutionStatus::Valid(_) | ExecutionStatus::Optimistic(_) => true,
// It's impossible for an irrelevant block to have completed the merge. It is pre-merge
// by definition.
// Pre-merge blocks have irrelevant execution status.
ExecutionStatus::Irrelevant(_) => false,
// If the parent has an invalid payload then it's impossible to build a valid block upon
// it. Reject the block.
@@ -381,7 +372,7 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
}
};
if is_merge_transition_complete || !execution_payload.is_default_with_empty_roots() {
if parent_has_execution || !execution_payload.is_default_with_empty_roots() {
let expected_timestamp = chain
.slot_clock
.start_of(block.slot())
@@ -430,7 +421,6 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
// task.
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, state.slot(), spec).map_err(BeaconStateError::from)?;
let random = *state.get_randao_mix(current_epoch)?;
@@ -438,7 +428,7 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
let latest_execution_payload_header_block_hash = latest_execution_payload_header.block_hash();
let latest_execution_payload_header_gas_limit = latest_execution_payload_header.gas_limit();
let withdrawals = if state.fork_name_unchecked().capella_enabled() {
Some(get_expected_withdrawals(state, spec)?.0.into())
Some(Withdrawals::<T::EthSpec>::from(get_expected_withdrawals(state, spec)?).into())
} else {
None
};
@@ -457,7 +447,6 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
async move {
prepare_execution_payload::<T>(
&chain,
is_merge_transition_complete,
timestamp,
random,
proposer_index,
@@ -481,8 +470,6 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
/// Prepares an execution payload for inclusion in a block.
///
/// Will return `Ok(None)` if the Bellatrix fork has occurred, but a terminal block has not been found.
///
/// ## Errors
///
/// Will return an error when using a pre-Bellatrix fork `state`. Ensure to only run this function
@@ -496,7 +483,6 @@ pub fn get_execution_payload<T: BeaconChainTypes>(
#[allow(clippy::too_many_arguments)]
pub async fn prepare_execution_payload<T>(
chain: &Arc<BeaconChain<T>>,
is_merge_transition_complete: bool,
timestamp: u64,
random: Hash256,
proposer_index: u64,
@@ -511,7 +497,6 @@ pub async fn prepare_execution_payload<T>(
where
T: BeaconChainTypes,
{
let current_epoch = builder_params.slot.epoch(T::EthSpec::slots_per_epoch());
let spec = &chain.spec;
let fork = spec.fork_name_at_slot::<T::EthSpec>(builder_params.slot);
let execution_layer = chain
@@ -519,42 +504,7 @@ where
.as_ref()
.ok_or(BlockProductionError::ExecutionLayerMissing)?;
let parent_hash = if !is_merge_transition_complete {
let is_terminal_block_hash_set = spec.terminal_block_hash != ExecutionBlockHash::zero();
let is_activation_epoch_reached =
current_epoch >= spec.terminal_block_hash_activation_epoch;
if is_terminal_block_hash_set && !is_activation_epoch_reached {
// Use the "empty" payload if there's a terminal block hash, but we haven't reached the
// terminal block epoch yet.
return Ok(BlockProposalContentsType::Full(
BlockProposalContents::Payload {
payload: FullPayload::default_at_fork(fork)?,
block_value: Uint256::ZERO,
},
));
}
let terminal_pow_block_hash = execution_layer
.get_terminal_pow_block_hash(spec, timestamp)
.await
.map_err(BlockProductionError::TerminalPoWBlockLookupFailed)?;
if let Some(terminal_pow_block_hash) = terminal_pow_block_hash {
terminal_pow_block_hash
} else {
// If the merge transition hasn't occurred yet and the EL hasn't found the terminal
// block, return an "empty" payload.
return Ok(BlockProposalContentsType::Full(
BlockProposalContents::Payload {
payload: FullPayload::default_at_fork(fork)?,
block_value: Uint256::ZERO,
},
));
}
} else {
latest_execution_payload_header_block_hash
};
let parent_hash = latest_execution_payload_header_block_hash;
// Try to obtain the fork choice update parameters from the cached head.
//
@@ -578,12 +528,20 @@ where
let suggested_fee_recipient = execution_layer
.get_suggested_fee_recipient(proposer_index)
.await;
let slot_number = if fork.gloas_enabled() {
Some(builder_params.slot.as_u64())
} else {
None
};
let payload_attributes = PayloadAttributes::new(
timestamp,
random,
suggested_fee_recipient,
withdrawals,
parent_beacon_block_root,
slot_number,
);
let target_gas_limit = execution_layer.get_proposer_gas_limit(proposer_index).await;