mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 21:04:41 +00:00
Use async code when interacting with EL (#3244)
## Overview
This rather extensive PR achieves two primary goals:
1. Uses the finalized/justified checkpoints of fork choice (FC), rather than that of the head state.
2. Refactors fork choice, block production and block processing to `async` functions.
Additionally, it achieves:
- Concurrent forkchoice updates to the EL and cache pruning after a new head is selected.
- Concurrent "block packing" (attestations, etc) and execution payload retrieval during block production.
- Concurrent per-block-processing and execution payload verification during block processing.
- The `Arc`-ification of `SignedBeaconBlock` during block processing (it's never mutated, so why not?):
- I had to do this to deal with sending blocks into spawned tasks.
- Previously we were cloning the beacon block at least 2 times during each block processing, these clones are either removed or turned into cheaper `Arc` clones.
- We were also `Box`-ing and un-`Box`-ing beacon blocks as they moved throughout the networking crate. This is not a big deal, but it's nice to avoid shifting things between the stack and heap.
- Avoids cloning *all the blocks* in *every chain segment* during sync.
- It also has the potential to clean up our code where we need to pass an *owned* block around so we can send it back in the case of an error (I didn't do much of this, my PR is already big enough 😅)
- The `BeaconChain::HeadSafetyStatus` struct was removed. It was an old relic from prior merge specs.
For motivation for this change, see https://github.com/sigp/lighthouse/pull/3244#issuecomment-1160963273
## Changes to `canonical_head` and `fork_choice`
Previously, the `BeaconChain` had two separate fields:
```
canonical_head: RwLock<Snapshot>,
fork_choice: RwLock<BeaconForkChoice>
```
Now, we have grouped these values under a single struct:
```
canonical_head: CanonicalHead {
cached_head: RwLock<Arc<Snapshot>>,
fork_choice: RwLock<BeaconForkChoice>
}
```
Apart from ergonomics, the only *actual* change here is wrapping the canonical head snapshot in an `Arc`. This means that we no longer need to hold the `cached_head` (`canonical_head`, in old terms) lock when we want to pull some values from it. This was done to avoid deadlock risks by preventing functions from acquiring (and holding) the `cached_head` and `fork_choice` locks simultaneously.
## Breaking Changes
### The `state` (root) field in the `finalized_checkpoint` SSE event
Consider the scenario where epoch `n` is just finalized, but `start_slot(n)` is skipped. There are two state roots we might in the `finalized_checkpoint` SSE event:
1. The state root of the finalized block, which is `get_block(finalized_checkpoint.root).state_root`.
4. The state root at slot of `start_slot(n)`, which would be the state from (1), but "skipped forward" through any skip slots.
Previously, Lighthouse would choose (2). However, we can see that when [Teku generates that event](de2b2801c8/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/EventSubscriptionManager.java (L171-L182)) it uses [`getStateRootFromBlockRoot`](de2b2801c8/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java (L336-L341)) which uses (1).
I have switched Lighthouse from (2) to (1). I think it's a somewhat arbitrary choice between the two, where (1) is easier to compute and is consistent with Teku.
## Notes for Reviewers
I've renamed `BeaconChain::fork_choice` to `BeaconChain::recompute_head`. Doing this helped ensure I broke all previous uses of fork choice and I also find it more descriptive. It describes an action and can't be confused with trying to get a reference to the `ForkChoice` struct.
I've changed the ordering of SSE events when a block is received. It used to be `[block, finalized, head]` and now it's `[block, head, finalized]`. It was easier this way and I don't think we were making any promises about SSE event ordering so it's not "breaking".
I've made it so fork choice will run when it's first constructed. I did this because I wanted to have a cached version of the last call to `get_head`. Ensuring `get_head` has been run *at least once* means that the cached values doesn't need to wrapped in an `Option`. This was fairly simple, it just involved passing a `slot` to the constructor so it knows *when* it's being run. When loading a fork choice from the store and a slot clock isn't handy I've just used the `slot` that was saved in the `fork_choice_store`. That seems like it would be a faithful representation of the slot when we saved it.
I added the `genesis_time: u64` to the `BeaconChain`. It's small, constant and nice to have around.
Since we're using FC for the fin/just checkpoints, we no longer get the `0x00..00` roots at genesis. You can see I had to remove a work-around in `ef-tests` here: b56be3bc2. I can't find any reason why this would be an issue, if anything I think it'll be better since the genesis-alias has caught us out a few times (0x00..00 isn't actually a real root). Edit: I did find a case where the `network` expected the 0x00..00 alias and patched it here: 3f26ac3e2.
You'll notice a lot of changes in tests. Generally, tests should be functionally equivalent. Here are the things creating the most diff-noise in tests:
- Changing tests to be `tokio::async` tests.
- Adding `.await` to fork choice, block processing and block production functions.
- Refactor of the `canonical_head` "API" provided by the `BeaconChain`. E.g., `chain.canonical_head.cached_head()` instead of `chain.canonical_head.read()`.
- Wrapping `SignedBeaconBlock` in an `Arc`.
- In the `beacon_chain/tests/block_verification`, we can't use the `lazy_static` `CHAIN_SEGMENT` variable anymore since it's generated with an async function. We just generate it in each test, not so efficient but hopefully insignificant.
I had to disable `rayon` concurrent tests in the `fork_choice` tests. This is because the use of `rayon` and `block_on` was causing a panic.
Co-authored-by: Mac L <mjladson@pm.me>
This commit is contained in:
@@ -21,8 +21,59 @@ use state_processing::per_block_processing::{
|
||||
partially_verify_execution_payload,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::task::JoinHandle;
|
||||
use types::*;
|
||||
|
||||
pub type PreparePayloadResult<Payload> = Result<Payload, BlockProductionError>;
|
||||
pub type PreparePayloadHandle<Payload> = JoinHandle<Option<PreparePayloadResult<Payload>>>;
|
||||
|
||||
/// Used to await the result of executing payload with a remote EE.
|
||||
pub struct PayloadNotifier<T: BeaconChainTypes> {
|
||||
pub chain: Arc<BeaconChain<T>>,
|
||||
pub block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
payload_verification_status: Option<PayloadVerificationStatus>,
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> PayloadNotifier<T> {
|
||||
pub fn new(
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
) -> 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(
|
||||
state,
|
||||
block.message().execution_payload()?,
|
||||
&chain.spec,
|
||||
)
|
||||
.map_err(BlockError::PerBlockProcessingError)?;
|
||||
None
|
||||
} else {
|
||||
Some(PayloadVerificationStatus::Irrelevant)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
chain,
|
||||
block,
|
||||
payload_verification_status,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn notify_new_payload(
|
||||
self,
|
||||
) -> Result<PayloadVerificationStatus, BlockError<T::EthSpec>> {
|
||||
if let Some(precomputed_status) = self.payload_verification_status {
|
||||
Ok(precomputed_status)
|
||||
} else {
|
||||
notify_new_payload(&self.chain, self.block.message()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that `execution_payload` contained by `block` is considered valid by an execution
|
||||
/// engine.
|
||||
///
|
||||
@@ -32,31 +83,20 @@ use types::*;
|
||||
/// 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
|
||||
pub fn notify_new_payload<T: BeaconChainTypes>(
|
||||
async fn notify_new_payload<'a, T: BeaconChainTypes>(
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
block: BeaconBlockRef<T::EthSpec>,
|
||||
block: BeaconBlockRef<'a, T::EthSpec>,
|
||||
) -> Result<PayloadVerificationStatus, BlockError<T::EthSpec>> {
|
||||
if !is_execution_enabled(state, block.body()) {
|
||||
return Ok(PayloadVerificationStatus::Irrelevant);
|
||||
}
|
||||
|
||||
let execution_payload = block.execution_payload()?;
|
||||
|
||||
// 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 payload from junk.
|
||||
partially_verify_execution_payload(state, execution_payload, &chain.spec)
|
||||
.map_err(BlockError::PerBlockProcessingError)?;
|
||||
|
||||
let execution_layer = chain
|
||||
.execution_layer
|
||||
.as_ref()
|
||||
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
|
||||
let new_payload_response = execution_layer.block_on(|execution_layer| {
|
||||
execution_layer.notify_new_payload(&execution_payload.execution_payload)
|
||||
});
|
||||
|
||||
let new_payload_response = execution_layer
|
||||
.notify_new_payload(&execution_payload.execution_payload)
|
||||
.await;
|
||||
|
||||
match new_payload_response {
|
||||
Ok(status) => match status {
|
||||
@@ -70,13 +110,13 @@ pub 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();
|
||||
chain.process_invalid_execution_payload(
|
||||
&InvalidationOperation::InvalidateMany {
|
||||
chain
|
||||
.process_invalid_execution_payload(&InvalidationOperation::InvalidateMany {
|
||||
head_block_root: latest_root,
|
||||
always_invalidate_head: false,
|
||||
latest_valid_ancestor: latest_valid_hash,
|
||||
},
|
||||
)?;
|
||||
})
|
||||
.await?;
|
||||
|
||||
Err(ExecutionPayloadError::RejectedByExecutionEngine { status }.into())
|
||||
}
|
||||
@@ -103,9 +143,9 @@ pub fn notify_new_payload<T: BeaconChainTypes>(
|
||||
/// 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 fn validate_merge_block<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
block: BeaconBlockRef<T::EthSpec>,
|
||||
pub async fn validate_merge_block<'a, T: BeaconChainTypes>(
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
block: BeaconBlockRef<'a, T::EthSpec>,
|
||||
) -> Result<(), BlockError<T::EthSpec>> {
|
||||
let spec = &chain.spec;
|
||||
let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
|
||||
@@ -137,9 +177,8 @@ pub fn validate_merge_block<T: BeaconChainTypes>(
|
||||
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
|
||||
|
||||
let is_valid_terminal_pow_block = execution_layer
|
||||
.block_on(|execution_layer| {
|
||||
execution_layer.is_valid_terminal_pow_block_hash(execution_payload.parent_hash(), spec)
|
||||
})
|
||||
.is_valid_terminal_pow_block_hash(execution_payload.parent_hash(), spec)
|
||||
.await
|
||||
.map_err(ExecutionPayloadError::from)?;
|
||||
|
||||
match is_valid_terminal_pow_block {
|
||||
@@ -149,23 +188,7 @@ pub fn validate_merge_block<T: BeaconChainTypes>(
|
||||
}
|
||||
.into()),
|
||||
None => {
|
||||
let current_slot = chain
|
||||
.slot_clock
|
||||
.now()
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
||||
|
||||
// Ensure the block is a candidate for optimistic import.
|
||||
if chain
|
||||
.fork_choice
|
||||
.read()
|
||||
.is_optimistic_candidate_block(
|
||||
current_slot,
|
||||
block.slot(),
|
||||
&block.parent_root(),
|
||||
&chain.spec,
|
||||
)
|
||||
.map_err(BeaconChainError::from)?
|
||||
{
|
||||
if is_optimistic_candidate_block(chain, block.slot(), block.parent_root()).await? {
|
||||
debug!(
|
||||
chain.log,
|
||||
"Optimistically accepting terminal block";
|
||||
@@ -180,6 +203,36 @@ pub fn validate_merge_block<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Check to see if a block with the given parameters is valid to be imported optimistically.
|
||||
pub async fn is_optimistic_candidate_block<T: BeaconChainTypes>(
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
block_slot: Slot,
|
||||
block_parent_root: Hash256,
|
||||
) -> Result<bool, BeaconChainError> {
|
||||
let current_slot = chain.slot()?;
|
||||
let inner_chain = chain.clone();
|
||||
|
||||
// Use a blocking task to check if the block is an optimistic candidate. Interacting
|
||||
// with the `fork_choice` lock in an async task can block the core executor.
|
||||
chain
|
||||
.spawn_blocking_handle(
|
||||
move || {
|
||||
inner_chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.is_optimistic_candidate_block(
|
||||
current_slot,
|
||||
block_slot,
|
||||
&block_parent_root,
|
||||
&inner_chain.spec,
|
||||
)
|
||||
},
|
||||
"validate_merge_block_optimistic_candidate",
|
||||
)
|
||||
.await?
|
||||
.map_err(BeaconChainError::from)
|
||||
}
|
||||
|
||||
/// Validate the gossip block's execution_payload according to the checks described here:
|
||||
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/p2p-interface.md#beacon_block
|
||||
pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
|
||||
@@ -243,35 +296,52 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
|
||||
/// Equivalent to the `get_execution_payload` function in the Validator Guide:
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal
|
||||
pub fn get_execution_payload<T: BeaconChainTypes, Payload: ExecPayload<T::EthSpec>>(
|
||||
chain: &BeaconChain<T>,
|
||||
pub fn get_execution_payload<
|
||||
T: BeaconChainTypes,
|
||||
Payload: ExecPayload<T::EthSpec> + Default + Send + 'static,
|
||||
>(
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
proposer_index: u64,
|
||||
pubkey: Option<PublicKeyBytes>,
|
||||
) -> Result<Payload, BlockProductionError> {
|
||||
Ok(
|
||||
prepare_execution_payload_blocking::<T, Payload>(chain, state, proposer_index, pubkey)?
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
) -> Result<PreparePayloadHandle<Payload>, BlockProductionError> {
|
||||
// Compute all required values from the `state` now to avoid needing to pass it into a spawned
|
||||
// task.
|
||||
let spec = &chain.spec;
|
||||
let slot = state.slot();
|
||||
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 random = *state.get_randao_mix(current_epoch)?;
|
||||
let latest_execution_payload_header_block_hash =
|
||||
state.latest_execution_payload_header()?.block_hash;
|
||||
|
||||
/// Wraps the async `prepare_execution_payload` function as a blocking task.
|
||||
pub fn prepare_execution_payload_blocking<T: BeaconChainTypes, Payload: ExecPayload<T::EthSpec>>(
|
||||
chain: &BeaconChain<T>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
proposer_index: u64,
|
||||
pubkey: Option<PublicKeyBytes>,
|
||||
) -> Result<Option<Payload>, BlockProductionError> {
|
||||
let execution_layer = chain
|
||||
.execution_layer
|
||||
.as_ref()
|
||||
.ok_or(BlockProductionError::ExecutionLayerMissing)?;
|
||||
// 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::<T, Payload>(
|
||||
&chain,
|
||||
slot,
|
||||
is_merge_transition_complete,
|
||||
timestamp,
|
||||
random,
|
||||
finalized_checkpoint,
|
||||
proposer_index,
|
||||
pubkey,
|
||||
latest_execution_payload_header_block_hash,
|
||||
)
|
||||
.await
|
||||
},
|
||||
"get_execution_payload",
|
||||
)
|
||||
.ok_or(BlockProductionError::ShuttingDown)?;
|
||||
|
||||
execution_layer
|
||||
.block_on_generic(|_| async {
|
||||
prepare_execution_payload::<T, Payload>(chain, state, proposer_index, pubkey).await
|
||||
})
|
||||
.map_err(BlockProductionError::BlockingFailed)?
|
||||
Ok(join_handle)
|
||||
}
|
||||
|
||||
/// Prepares an execution payload for inclusion in a block.
|
||||
@@ -288,25 +358,38 @@ pub fn prepare_execution_payload_blocking<T: BeaconChainTypes, Payload: ExecPayl
|
||||
/// 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<T: BeaconChainTypes, Payload: ExecPayload<T::EthSpec>>(
|
||||
chain: &BeaconChain<T>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn prepare_execution_payload<T, Payload>(
|
||||
chain: &Arc<BeaconChain<T>>,
|
||||
slot: Slot,
|
||||
is_merge_transition_complete: bool,
|
||||
timestamp: u64,
|
||||
random: Hash256,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
proposer_index: u64,
|
||||
pubkey: Option<PublicKeyBytes>,
|
||||
) -> Result<Option<Payload>, BlockProductionError> {
|
||||
latest_execution_payload_header_block_hash: ExecutionBlockHash,
|
||||
) -> Result<Payload, BlockProductionError>
|
||||
where
|
||||
T: BeaconChainTypes,
|
||||
Payload: ExecPayload<T::EthSpec> + Default,
|
||||
{
|
||||
let current_epoch = slot.epoch(T::EthSpec::slots_per_epoch());
|
||||
let spec = &chain.spec;
|
||||
let execution_layer = chain
|
||||
.execution_layer
|
||||
.as_ref()
|
||||
.ok_or(BlockProductionError::ExecutionLayerMissing)?;
|
||||
|
||||
let parent_hash = if !is_merge_transition_complete(state) {
|
||||
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 =
|
||||
state.current_epoch() >= spec.terminal_block_hash_activation_epoch;
|
||||
current_epoch >= spec.terminal_block_hash_activation_epoch;
|
||||
|
||||
if is_terminal_block_hash_set && !is_activation_epoch_reached {
|
||||
return Ok(None);
|
||||
// Use the "empty" payload if there's a terminal block hash, but we haven't reached the
|
||||
// terminal block epoch yet.
|
||||
return Ok(<_>::default());
|
||||
}
|
||||
|
||||
let terminal_pow_block_hash = execution_layer
|
||||
@@ -317,36 +400,55 @@ pub async fn prepare_execution_payload<T: BeaconChainTypes, Payload: ExecPayload
|
||||
if let Some(terminal_pow_block_hash) = terminal_pow_block_hash {
|
||||
terminal_pow_block_hash
|
||||
} else {
|
||||
return Ok(None);
|
||||
// If the merge transition hasn't occurred yet and the EL hasn't found the terminal
|
||||
// block, return an "empty" payload.
|
||||
return Ok(<_>::default());
|
||||
}
|
||||
} else {
|
||||
state.latest_execution_payload_header()?.block_hash
|
||||
latest_execution_payload_header_block_hash
|
||||
};
|
||||
|
||||
let timestamp = compute_timestamp_at_slot(state, spec).map_err(BeaconStateError::from)?;
|
||||
let random = *state.get_randao_mix(state.current_epoch())?;
|
||||
let finalized_root = state.finalized_checkpoint().root;
|
||||
// Try to obtain the finalized proto block from fork choice.
|
||||
//
|
||||
// Use a blocking task to interact with the `fork_choice` lock otherwise we risk blocking the
|
||||
// core `tokio` executor.
|
||||
let inner_chain = chain.clone();
|
||||
let finalized_proto_block = chain
|
||||
.spawn_blocking_handle(
|
||||
move || {
|
||||
inner_chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block(&finalized_checkpoint.root)
|
||||
},
|
||||
"prepare_execution_payload_finalized_hash",
|
||||
)
|
||||
.await
|
||||
.map_err(BlockProductionError::BeaconChain)?;
|
||||
|
||||
// The finalized block hash is not included in the specification, however we provide this
|
||||
// parameter so that the execution layer can produce a payload id if one is not already known
|
||||
// (e.g., due to a recent reorg).
|
||||
let finalized_block_hash =
|
||||
if let Some(block) = chain.fork_choice.read().get_block(&finalized_root) {
|
||||
block.execution_status.block_hash()
|
||||
} else {
|
||||
chain
|
||||
.store
|
||||
.get_blinded_block(&finalized_root)
|
||||
.map_err(BlockProductionError::FailedToReadFinalizedBlock)?
|
||||
.ok_or(BlockProductionError::MissingFinalizedBlock(finalized_root))?
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.ok()
|
||||
.map(|ep| ep.block_hash())
|
||||
};
|
||||
let finalized_block_hash = if let Some(block) = finalized_proto_block {
|
||||
block.execution_status.block_hash()
|
||||
} else {
|
||||
chain
|
||||
.store
|
||||
.get_blinded_block(&finalized_checkpoint.root)
|
||||
.map_err(BlockProductionError::FailedToReadFinalizedBlock)?
|
||||
.ok_or(BlockProductionError::MissingFinalizedBlock(
|
||||
finalized_checkpoint.root,
|
||||
))?
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.ok()
|
||||
.map(|ep| ep.block_hash())
|
||||
};
|
||||
|
||||
// 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 execution_payload = execution_layer
|
||||
.get_payload::<Payload>(
|
||||
parent_hash,
|
||||
@@ -355,10 +457,10 @@ pub async fn prepare_execution_payload<T: BeaconChainTypes, Payload: ExecPayload
|
||||
finalized_block_hash.unwrap_or_else(ExecutionBlockHash::zero),
|
||||
proposer_index,
|
||||
pubkey,
|
||||
state.slot(),
|
||||
slot,
|
||||
)
|
||||
.await
|
||||
.map_err(BlockProductionError::GetPayloadFailed)?;
|
||||
|
||||
Ok(Some(execution_payload))
|
||||
Ok(execution_payload)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user