Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add block v3 endpoint #4594

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,121 changes: 973 additions & 148 deletions beacon_node/beacon_chain/src/beacon_chain.rs

Large diffs are not rendered by default.

214 changes: 213 additions & 1 deletion beacon_node/beacon_chain/src/execution_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use crate::{
BeaconChain, BeaconChainError, BeaconChainTypes, BlockError, BlockProductionError,
ExecutionPayloadError,
};
use execution_layer::{BlockProposalContents, BuilderParams, PayloadAttributes, PayloadStatus};
use execution_layer::{
BlockProposalContentsType, BlockProposalContents, BuilderParams, ExecutionLayer,
PayloadAttributes, PayloadStatus,
};
use fork_choice::{InvalidationOperation, PayloadVerificationStatus};
use proto_array::{Block as ProtoBlock, ExecutionStatus};
use slog::{debug, warn};
Expand All @@ -26,6 +29,9 @@ use tokio::task::JoinHandle;
use tree_hash::TreeHash;
use types::*;

pub type PreparePayloadResultV3<E> = Result<BlockProposalContentsType<E>, BlockProductionError>;
pub type PreparePayloadHandleV3<E> = JoinHandle<Option<PreparePayloadResultV3<E>>>;

pub type PreparePayloadResult<E, Payload> =
Result<BlockProposalContents<E, Payload>, BlockProductionError>;
pub type PreparePayloadHandle<E, Payload> = JoinHandle<Option<PreparePayloadResult<E, Payload>>>;
Expand All @@ -46,6 +52,17 @@ pub enum NotifyExecutionLayer {
No,
}

pub struct ExecutionPayloadInput<T: BeaconChainTypes> {
chain: Arc<BeaconChain<T>>,
is_merge_transition_complete: bool,
timestamp: u64,
random: Hash256,
proposer_index: u64,
latest_execution_payload_header_block_hash: ExecutionBlockHash,
builder_params: BuilderParams,
withdrawals: Option<Vec<Withdrawal>>,
}

/// Used to await the result of executing payload with a remote EE.
pub struct PayloadNotifier<T: BeaconChainTypes> {
pub chain: Arc<BeaconChain<T>>,
Expand Down Expand Up @@ -387,6 +404,68 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
Ok(())
}

pub fn prepare_execution_payload_input<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
state: &BeaconState<T::EthSpec>,
proposer_index: u64,
builder_params: BuilderParams,
) -> Result<ExecutionPayloadInput<T>, BlockProductionError> {
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)?;
let latest_execution_payload_header_block_hash =
state.latest_execution_payload_header()?.block_hash();
let withdrawals = match state {
&BeaconState::Capella(_) => Some(get_expected_withdrawals(state, spec)?.into()),
&BeaconState::Merge(_) => None,
// These shouldn't happen but they're here to make the pattern irrefutable
&BeaconState::Base(_) | &BeaconState::Altair(_) => None,
};

let execution_payload_input = ExecutionPayloadInput {
chain,
is_merge_transition_complete,
timestamp,
random,
proposer_index,
latest_execution_payload_header_block_hash,
builder_params,
withdrawals,
};

Ok(execution_payload_input)
}

pub fn get_execution_payload_v3<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
state: &BeaconState<T::EthSpec>,
proposer_index: u64,
builder_params: BuilderParams,
) -> Result<PreparePayloadHandleV3<T::EthSpec>, BlockProductionError> {
// Compute all required values from the `state` now to avoid needing to pass it into a spawned
// task.
let execution_payload_input =
prepare_execution_payload_input::<T>(chain, state, proposer_index, builder_params)?;

// Spawn a task to obtain the execution payload from the EL via a series of async calls. The
// `join_handle` can be used to await the result of the function.
let join_handle = chain
.task_executor
.clone()
.spawn_handle(
async move {
prepare_execution_payload_v3::<T>(execution_payload_input).await
},
"get_execution_payload",
)
.ok_or(BlockProductionError::ShuttingDown)?;

Ok(join_handle)
}

/// Gets an execution payload for inclusion in a block.
///
/// ## Errors
Expand Down Expand Up @@ -451,6 +530,139 @@ pub fn get_execution_payload<
Ok(join_handle)
}

pub async fn get_parent_execution_block_hash<T: BeaconChainTypes>(
execution_layer: &ExecutionLayer<<T as BeaconChainTypes>::EthSpec>,
current_epoch: Epoch,
spec: &ChainSpec,
timestamp: u64,
latest_execution_payload_header_block_hash: ExecutionBlockHash,
is_merge_transition_complete: bool,
) -> Result<Option<ExecutionBlockHash>, BlockProductionError> {
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 {
// if there's a terminal block hash, but we haven't reached the
// terminal block epoch yet return None to indicate that we should use the "empty" payload.
return Ok(None);
}

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 None to indicate that we should return an "empty" payload.
return Ok(None);
}
} else {
latest_execution_payload_header_block_hash
};

Ok(Some(parent_hash))
}

/// Prepares an execution payload for inclusion in a block.
///
/// Will return `Ok(None)` if the merge fork has occurred, but a terminal block has not been found.
///
/// ## 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 `prepare_execution_payload` function in the Validator Guide:
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal
pub async fn prepare_execution_payload_v3<T>(
execution_payload_input: ExecutionPayloadInput<T>,
) -> Result<BlockProposalContentsType<T::EthSpec>, BlockProductionError>
where
T: BeaconChainTypes,
{
let current_epoch = execution_payload_input
.builder_params
.slot
.epoch(T::EthSpec::slots_per_epoch());
let spec = &execution_payload_input.chain.spec;
let fork = spec.fork_name_at_slot::<T::EthSpec>(execution_payload_input.builder_params.slot);
let execution_layer = execution_payload_input
.chain
.execution_layer
.as_ref()
.ok_or(BlockProductionError::ExecutionLayerMissing)?;

let parent_hash = match get_parent_execution_block_hash::<T>(
execution_layer,
current_epoch,
spec,
execution_payload_input.timestamp,
execution_payload_input.latest_execution_payload_header_block_hash,
execution_payload_input.is_merge_transition_complete,
)
.await? {
Some(parent_hash) => parent_hash,
// we either haven't reached the terminal block hash
// or the EL hasn't found the terminal block
// so we return an "empty" payload.
None => return BlockProposalContentsType::default_at_fork(fork).map_err(Into::into),
};

// Try to obtain the fork choice update parameters from the cached head.
//
// Use a blocking task to interact with the `canonical_head` lock otherwise we risk blocking the
// core `tokio` executor.
let inner_chain = execution_payload_input.chain.clone();
let forkchoice_update_params = execution_payload_input
.chain
.spawn_blocking_handle(
move || {
inner_chain
.canonical_head
.cached_head()
.forkchoice_update_parameters()
},
"prepare_execution_payload_forkchoice_update_params",
)
.await
.map_err(BlockProductionError::BeaconChain)?;

let suggested_fee_recipient = execution_layer
.get_suggested_fee_recipient(execution_payload_input.proposer_index)
.await;
let payload_attributes = PayloadAttributes::new(
execution_payload_input.timestamp,
execution_payload_input.random,
suggested_fee_recipient,
execution_payload_input.withdrawals,
);

// 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_v3(
parent_hash,
&payload_attributes,
forkchoice_update_params,
execution_payload_input.builder_params,
fork,
&execution_payload_input.chain.spec,
)
.await
.map_err(BlockProductionError::GetPayloadFailed)?;

Ok(block_contents)
}

/// Prepares an execution payload for inclusion in a block.
///
/// Will return `Ok(None)` if the merge fork has occurred, but a terminal block has not been found.
Expand Down
4 changes: 2 additions & 2 deletions beacon_node/beacon_chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ where

let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot);

let (block, state) = self
let (block, state, _) = self
.chain
.produce_block_on_state(
state,
Expand Down Expand Up @@ -819,7 +819,7 @@ where

let pre_state = state.clone();

let (block, state) = self
let (block, state, _) = self
.chain
.produce_block_on_state(
state,
Expand Down
Loading