Block v3 endpoint (#4629)

## Issue Addressed

#4582

## Proposed Changes

Add a new v3 block fetching flow that can decide to return a Full OR Blinded payload

## Additional Info



Co-authored-by: Michael Sproul <micsproul@gmail.com>
This commit is contained in:
Eitan Seri-Levi 2023-11-03 00:12:18 +00:00
parent 42da392edc
commit 07f53b18fc
20 changed files with 972 additions and 459 deletions

View File

@ -33,6 +33,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
state.build_committee_cache(RelativeEpoch::Previous, &self.spec)?;
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
self.compute_beacon_block_reward_with_cache(block, block_root, state)
}
// This should only be called after a committee cache has been built
// for both the previous and current epoch
fn compute_beacon_block_reward_with_cache<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
block_root: Hash256,
state: &BeaconState<T::EthSpec>,
) -> Result<StandardBlockReward, BeaconChainError> {
let proposer_index = block.proposer_index();
let sync_aggregate_reward =
@ -178,7 +189,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &mut BeaconState<T::EthSpec>,
state: &BeaconState<T::EthSpec>,
) -> Result<BeaconBlockSubRewardValue, BeaconChainError> {
let total_active_balance = state.get_total_active_balance()?;
let base_reward_per_increment =
@ -191,6 +202,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.safe_mul(WEIGHT_DENOMINATOR)?
.safe_div(PROPOSER_WEIGHT)?;
let mut current_epoch_participation = state.current_epoch_participation()?.clone();
let mut previous_epoch_participation = state.previous_epoch_participation()?.clone();
for attestation in block.body().attestations() {
let data = &attestation.data;
let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64();
@ -203,13 +217,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
)?;
let attesting_indices = get_attesting_indices_from_state(state, attestation)?;
let mut proposer_reward_numerator = 0;
for index in attesting_indices {
let index = index as usize;
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
let epoch_participation =
state.get_epoch_participation_mut(data.target.epoch)?;
let epoch_participation = if data.target.epoch == state.current_epoch() {
&mut current_epoch_participation
} else {
&mut previous_epoch_participation
};
let validator_participation = epoch_participation
.get_mut(index)
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;

View File

@ -72,8 +72,8 @@ use crate::{
};
use eth2::types::{EventKind, SseBlobSidecar, SseBlock, SseExtendedPayloadAttributes, SyncDuty};
use execution_layer::{
BlockProposalContents, BuilderParams, ChainHealth, ExecutionLayer, FailedCondition,
PayloadAttributes, PayloadStatus,
BlockProposalContents, BlockProposalContentsType, BuilderParams, ChainHealth, ExecutionLayer,
FailedCondition, PayloadAttributes, PayloadStatus,
};
use fork_choice::{
AttestationFromBlock, ExecutionStatus, ForkChoice, ForkchoiceUpdateParameters,
@ -120,6 +120,7 @@ use tokio_stream::Stream;
use tree_hash::TreeHash;
use types::beacon_state::CloneConfig;
use types::blob_sidecar::{BlobSidecarList, FixedBlobSidecarList};
use types::payload::BlockProductionVersion;
use types::sidecar::BlobItems;
use types::*;
@ -320,8 +321,7 @@ pub trait BeaconChainTypes: Send + Sync + 'static {
type EthSpec: types::EthSpec;
}
/// Used internally to split block production into discrete functions.
struct PartialBeaconBlock<E: EthSpec, Payload: AbstractExecPayload<E>> {
struct PartialBeaconBlock<E: EthSpec> {
state: BeaconState<E>,
slot: Slot,
proposer_index: u64,
@ -335,7 +335,7 @@ struct PartialBeaconBlock<E: EthSpec, Payload: AbstractExecPayload<E>> {
deposits: Vec<Deposit>,
voluntary_exits: Vec<SignedVoluntaryExit>,
sync_aggregate: Option<SyncAggregate<E>>,
prepare_payload_handle: Option<PreparePayloadHandle<E, Payload>>,
prepare_payload_handle: Option<PreparePayloadHandle<E>>,
bls_to_execution_changes: Vec<SignedBlsToExecutionChange>,
}
@ -484,11 +484,18 @@ pub struct BeaconChain<T: BeaconChainTypes> {
pub kzg: Option<Arc<Kzg>>,
}
type BeaconBlockAndState<T, Payload> = (
BeaconBlock<T, Payload>,
BeaconState<T>,
Option<SidecarList<T, <Payload as AbstractExecPayload<T>>::Sidecar>>,
);
pub enum BeaconBlockResponseType<T: EthSpec> {
Full(BeaconBlockResponse<T, FullPayload<T>>),
Blinded(BeaconBlockResponse<T, BlindedPayload<T>>),
}
pub struct BeaconBlockResponse<T: EthSpec, Payload: AbstractExecPayload<T>> {
pub block: BeaconBlock<T, Payload>,
pub state: BeaconState<T>,
pub maybe_side_car: Option<SidecarList<T, <Payload as AbstractExecPayload<T>>::Sidecar>>,
pub execution_payload_value: Option<Uint256>,
pub consensus_block_value: Option<u64>,
}
impl FinalizationAndCanonicity {
pub fn is_finalized(self) -> bool {
@ -3949,38 +3956,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(())
}
/// Produce a new block at the given `slot`.
///
/// The produced block will not be inherently valid, it must be signed by a block producer.
/// Block signing is out of the scope of this function and should be done by a separate program.
pub async fn produce_block<Payload: AbstractExecPayload<T::EthSpec> + 'static>(
self: &Arc<Self>,
randao_reveal: Signature,
slot: Slot,
validator_graffiti: Option<Graffiti>,
) -> Result<BeaconBlockAndState<T::EthSpec, Payload>, BlockProductionError> {
self.produce_block_with_verification(
randao_reveal,
slot,
validator_graffiti,
ProduceBlockVerification::VerifyRandao,
)
.await
}
/// Same as `produce_block` but allowing for configuration of RANDAO-verification.
pub async fn produce_block_with_verification<
Payload: AbstractExecPayload<T::EthSpec> + 'static,
>(
pub async fn produce_block_with_verification(
self: &Arc<Self>,
randao_reveal: Signature,
slot: Slot,
validator_graffiti: Option<Graffiti>,
verification: ProduceBlockVerification,
) -> Result<BeaconBlockAndState<T::EthSpec, Payload>, BlockProductionError> {
block_production_version: BlockProductionVersion,
) -> Result<BeaconBlockResponseType<T::EthSpec>, BlockProductionError> {
metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS);
let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES);
// Part 1/2 (blocking)
//
// Load the parent state from disk.
@ -3998,13 +3983,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// Part 2/2 (async, with some blocking components)
//
// Produce the block upon the state
self.produce_block_on_state::<Payload>(
self.produce_block_on_state(
state,
state_root_opt,
slot,
randao_reveal,
validator_graffiti,
verification,
block_production_version,
)
.await
}
@ -4568,7 +4554,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// The provided `state_root_opt` should only ever be set to `Some` if the contained value is
/// equal to the root of `state`. Providing this value will serve as an optimization to avoid
/// performing a tree hash in some scenarios.
pub async fn produce_block_on_state<Payload: AbstractExecPayload<T::EthSpec> + 'static>(
#[allow(clippy::too_many_arguments)]
pub async fn produce_block_on_state(
self: &Arc<Self>,
state: BeaconState<T::EthSpec>,
state_root_opt: Option<Hash256>,
@ -4576,7 +4563,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
randao_reveal: Signature,
validator_graffiti: Option<Graffiti>,
verification: ProduceBlockVerification,
) -> Result<BeaconBlockAndState<T::EthSpec, Payload>, BlockProductionError> {
block_production_version: BlockProductionVersion,
) -> Result<BeaconBlockResponseType<T::EthSpec>, BlockProductionError> {
// Part 1/3 (blocking)
//
// Perform the state advance and block-packing functions.
@ -4591,6 +4579,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
produce_at_slot,
randao_reveal,
validator_graffiti,
block_production_version,
)
},
"produce_partial_beacon_block",
@ -4598,50 +4587,96 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.ok_or(BlockProductionError::ShuttingDown)?
.await
.map_err(BlockProductionError::TokioJoin)??;
// Part 2/3 (async)
//
// Wait for the execution layer to return an execution payload (if one is required).
let prepare_payload_handle = partial_beacon_block.prepare_payload_handle.take();
let block_contents = if let Some(prepare_payload_handle) = prepare_payload_handle {
Some(
prepare_payload_handle
.await
.map_err(BlockProductionError::TokioJoin)?
.ok_or(BlockProductionError::ShuttingDown)??,
)
} else {
None
};
let block_contents_type_option =
if let Some(prepare_payload_handle) = prepare_payload_handle {
Some(
prepare_payload_handle
.await
.map_err(BlockProductionError::TokioJoin)?
.ok_or(BlockProductionError::ShuttingDown)??,
)
} else {
None
};
// Part 3/3 (blocking)
//
// Perform the final steps of combining all the parts and computing the state root.
let chain = self.clone();
self.task_executor
.spawn_blocking_handle(
move || {
chain.complete_partial_beacon_block(
partial_beacon_block,
block_contents,
verification,
)
},
"complete_partial_beacon_block",
)
.ok_or(BlockProductionError::ShuttingDown)?
.await
.map_err(BlockProductionError::TokioJoin)?
if let Some(block_contents_type) = block_contents_type_option {
match block_contents_type {
BlockProposalContentsType::Full(block_contents) => {
let chain = self.clone();
let beacon_block_response = self
.task_executor
.spawn_blocking_handle(
move || {
chain.complete_partial_beacon_block(
partial_beacon_block,
Some(block_contents),
verification,
)
},
"complete_partial_beacon_block",
)
.ok_or(BlockProductionError::ShuttingDown)?
.await
.map_err(BlockProductionError::TokioJoin)??;
Ok(BeaconBlockResponseType::Full(beacon_block_response))
}
BlockProposalContentsType::Blinded(block_contents) => {
let chain = self.clone();
let beacon_block_response = self
.task_executor
.spawn_blocking_handle(
move || {
chain.complete_partial_beacon_block(
partial_beacon_block,
Some(block_contents),
verification,
)
},
"complete_partial_beacon_block",
)
.ok_or(BlockProductionError::ShuttingDown)?
.await
.map_err(BlockProductionError::TokioJoin)??;
Ok(BeaconBlockResponseType::Blinded(beacon_block_response))
}
}
} else {
let chain = self.clone();
let beacon_block_response = self
.task_executor
.spawn_blocking_handle(
move || {
chain.complete_partial_beacon_block(
partial_beacon_block,
None,
verification,
)
},
"complete_partial_beacon_block",
)
.ok_or(BlockProductionError::ShuttingDown)?
.await
.map_err(BlockProductionError::TokioJoin)??;
Ok(BeaconBlockResponseType::Full(beacon_block_response))
}
}
fn produce_partial_beacon_block<Payload: AbstractExecPayload<T::EthSpec> + 'static>(
fn produce_partial_beacon_block(
self: &Arc<Self>,
mut state: BeaconState<T::EthSpec>,
state_root_opt: Option<Hash256>,
produce_at_slot: Slot,
randao_reveal: Signature,
validator_graffiti: Option<Graffiti>,
) -> Result<PartialBeaconBlock<T::EthSpec, Payload>, BlockProductionError> {
block_production_version: BlockProductionVersion,
) -> Result<PartialBeaconBlock<T::EthSpec>, BlockProductionError> {
let eth1_chain = self
.eth1_chain
.as_ref()
@ -4701,6 +4736,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
parent_root,
proposer_index,
builder_params,
block_production_version,
)?;
Some(prepare_payload_handle)
}
@ -4710,6 +4746,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.op_pool.get_slashings_and_exits(&state, &self.spec);
let eth1_data = eth1_chain.eth1_data_for_block_production(&state, &self.spec)?;
let deposits = eth1_chain.deposits_for_block_inclusion(&state, &eth1_data, &self.spec)?;
let bls_to_execution_changes = self
@ -4880,10 +4917,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
fn complete_partial_beacon_block<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
partial_beacon_block: PartialBeaconBlock<T::EthSpec, Payload>,
partial_beacon_block: PartialBeaconBlock<T::EthSpec>,
block_contents: Option<BlockProposalContents<T::EthSpec, Payload>>,
verification: ProduceBlockVerification,
) -> Result<BeaconBlockAndState<T::EthSpec, Payload>, BlockProductionError> {
) -> Result<BeaconBlockResponse<T::EthSpec, Payload>, BlockProductionError> {
let PartialBeaconBlock {
mut state,
slot,
@ -4905,7 +4942,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
bls_to_execution_changes,
} = partial_beacon_block;
let (inner_block, blobs_opt, proofs_opt) = match &state {
let (inner_block, blobs_opt, proofs_opt, execution_payload_value) = match &state {
BeaconState::Base(_) => (
BeaconBlock::Base(BeaconBlockBase {
slot,
@ -4926,6 +4963,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}),
None,
None,
Uint256::zero(),
),
BeaconState::Altair(_) => (
BeaconBlock::Altair(BeaconBlockAltair {
@ -4949,11 +4987,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}),
None,
None,
Uint256::zero(),
),
BeaconState::Merge(_) => {
let (payload, _, _, _) = block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.deconstruct();
let block_proposal_contents =
block_contents.ok_or(BlockProductionError::MissingExecutionPayload)?;
let execution_payload_value = block_proposal_contents.block_value().to_owned();
(
BeaconBlock::Merge(BeaconBlockMerge {
slot,
@ -4971,19 +5010,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
voluntary_exits: voluntary_exits.into(),
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: payload
execution_payload: block_proposal_contents
.to_payload()
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
},
}),
None,
None,
execution_payload_value,
)
}
BeaconState::Capella(_) => {
let (payload, _, _, _) = block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.deconstruct();
let block_proposal_contents =
block_contents.ok_or(BlockProductionError::MissingExecutionPayload)?;
let execution_payload_value = block_proposal_contents.block_value().to_owned();
(
BeaconBlock::Capella(BeaconBlockCapella {
slot,
@ -5001,7 +5043,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
voluntary_exits: voluntary_exits.into(),
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: payload
execution_payload: block_proposal_contents
.to_payload()
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
bls_to_execution_changes: bls_to_execution_changes.into(),
@ -5009,12 +5052,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}),
None,
None,
execution_payload_value,
)
}
BeaconState::Deneb(_) => {
let (payload, kzg_commitments, blobs, proofs) = block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.deconstruct();
let (payload, kzg_commitments, blobs, proofs, execution_payload_value) =
block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.deconstruct();
(
BeaconBlock::Deneb(BeaconBlockDeneb {
slot,
@ -5042,6 +5088,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}),
blobs,
proofs,
execution_payload_value,
)
}
};
@ -5057,7 +5104,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.log,
"Produced block on state";
"block_size" => block_size,
"slot" => block.slot(),
);
metrics::observe(&metrics::BLOCK_SIZE, block_size as f64);
@ -5075,6 +5121,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// Use a context without block root or proposer index so that both are checked.
let mut ctxt = ConsensusContext::new(block.slot());
let consensus_block_value = self
.compute_beacon_block_reward(block.message(), Hash256::zero(), &mut state)
.map(|reward| reward.total)
.unwrap_or(0);
per_block_processing(
&mut state,
&block,
@ -5154,7 +5205,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
"slot" => block.slot()
);
Ok((block, state, maybe_sidecar_list))
Ok(BeaconBlockResponse {
block,
state,
maybe_side_car: maybe_sidecar_list,
execution_payload_value: Some(execution_payload_value),
consensus_block_value: Some(consensus_block_value),
})
}
/// This method must be called whenever an execution engine indicates that a payload is

View File

@ -13,7 +13,8 @@ use crate::{
ExecutionPayloadError,
};
use execution_layer::{
BlockProposalContents, BuilderParams, NewPayloadRequest, PayloadAttributes, PayloadStatus,
BlockProposalContents, BlockProposalContentsType, BuilderParams, NewPayloadRequest,
PayloadAttributes, PayloadStatus,
};
use fork_choice::{InvalidationOperation, PayloadVerificationStatus};
use proto_array::{Block as ProtoBlock, ExecutionStatus};
@ -26,11 +27,11 @@ use state_processing::per_block_processing::{
use std::sync::Arc;
use tokio::task::JoinHandle;
use tree_hash::TreeHash;
use types::payload::BlockProductionVersion;
use types::*;
pub type PreparePayloadResult<E, Payload> =
Result<BlockProposalContents<E, Payload>, BlockProductionError>;
pub type PreparePayloadHandle<E, Payload> = JoinHandle<Option<PreparePayloadResult<E, Payload>>>;
pub type PreparePayloadResult<E> = Result<BlockProposalContentsType<E>, BlockProductionError>;
pub type PreparePayloadHandle<E> = JoinHandle<Option<PreparePayloadResult<E>>>;
#[derive(PartialEq)]
pub enum AllowOptimisticImport {
@ -398,16 +399,14 @@ 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: AbstractExecPayload<T::EthSpec> + 'static,
>(
pub fn get_execution_payload<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
state: &BeaconState<T::EthSpec>,
parent_block_root: Hash256,
proposer_index: u64,
builder_params: BuilderParams,
) -> Result<PreparePayloadHandle<T::EthSpec, Payload>, BlockProductionError> {
block_production_version: BlockProductionVersion,
) -> Result<PreparePayloadHandle<T::EthSpec>, BlockProductionError> {
// Compute all required values from the `state` now to avoid needing to pass it into a spawned
// task.
let spec = &chain.spec;
@ -440,7 +439,7 @@ pub fn get_execution_payload<
.clone()
.spawn_handle(
async move {
prepare_execution_payload::<T, Payload>(
prepare_execution_payload::<T>(
&chain,
is_merge_transition_complete,
timestamp,
@ -450,6 +449,7 @@ pub fn get_execution_payload<
builder_params,
withdrawals,
parent_beacon_block_root,
block_production_version,
)
.await
},
@ -475,7 +475,7 @@ pub fn get_execution_payload<
///
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal
#[allow(clippy::too_many_arguments)]
pub async fn prepare_execution_payload<T, Payload>(
pub async fn prepare_execution_payload<T>(
chain: &Arc<BeaconChain<T>>,
is_merge_transition_complete: bool,
timestamp: u64,
@ -485,10 +485,10 @@ pub async fn prepare_execution_payload<T, Payload>(
builder_params: BuilderParams,
withdrawals: Option<Vec<Withdrawal>>,
parent_beacon_block_root: Option<Hash256>,
) -> Result<BlockProposalContents<T::EthSpec, Payload>, BlockProductionError>
block_production_version: BlockProductionVersion,
) -> Result<BlockProposalContentsType<T::EthSpec>, BlockProductionError>
where
T: BeaconChainTypes,
Payload: AbstractExecPayload<T::EthSpec>,
{
let current_epoch = builder_params.slot.epoch(T::EthSpec::slots_per_epoch());
let spec = &chain.spec;
@ -506,7 +506,12 @@ where
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 BlockProposalContents::default_at_fork(fork).map_err(Into::into);
return Ok(BlockProposalContentsType::Full(
BlockProposalContents::Payload {
payload: FullPayload::default_at_fork(fork)?,
block_value: Uint256::zero(),
},
));
}
let terminal_pow_block_hash = execution_layer
@ -519,7 +524,12 @@ where
} else {
// If the merge transition hasn't occurred yet and the EL hasn't found the terminal
// block, return an "empty" payload.
return BlockProposalContents::default_at_fork(fork).map_err(Into::into);
return Ok(BlockProposalContentsType::Full(
BlockProposalContents::Payload {
payload: FullPayload::default_at_fork(fork)?,
block_value: Uint256::zero(),
},
));
}
} else {
latest_execution_payload_header_block_hash
@ -558,13 +568,14 @@ where
//
// This future is not executed here, it's up to the caller to await it.
let block_contents = execution_layer
.get_payload::<Payload>(
.get_payload(
parent_hash,
&payload_attributes,
forkchoice_update_params,
builder_params,
fork,
&chain.spec,
block_production_version,
)
.await
.map_err(BlockProductionError::GetPayloadFailed)?;

View File

@ -56,10 +56,10 @@ pub mod validator_monitor;
pub mod validator_pubkey_cache;
pub use self::beacon_chain::{
AttestationProcessingOutcome, AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes,
BeaconStore, ChainSegmentResult, ForkChoiceError, OverrideForkchoiceUpdate,
ProduceBlockVerification, StateSkipConfig, WhenSlotSkipped,
INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON,
AttestationProcessingOutcome, AvailabilityProcessingStatus, BeaconBlockResponse,
BeaconBlockResponseType, BeaconChain, BeaconChainTypes, BeaconStore, ChainSegmentResult,
ForkChoiceError, OverrideForkchoiceUpdate, ProduceBlockVerification, StateSkipConfig,
WhenSlotSkipped, INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON,
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON,
};
pub use self::beacon_snapshot::BeaconSnapshot;

View File

@ -1,6 +1,7 @@
use crate::block_verification_types::{AsBlock, RpcBlock};
use crate::observed_operations::ObservationOutcome;
pub use crate::persisted_beacon_chain::PersistedBeaconChain;
use crate::BeaconBlockResponseType;
pub use crate::{
beacon_chain::{BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY},
migrate::MigratorConfig,
@ -60,6 +61,7 @@ use store::{config::StoreConfig, HotColdDB, ItemStore, LevelDB, MemoryStore};
use task_executor::TaskExecutor;
use task_executor::{test_utils::TestRuntime, ShutdownReason};
use tree_hash::TreeHash;
use types::payload::BlockProductionVersion;
use types::sync_selection_proof::SyncSelectionProof;
pub use types::test_utils::generate_deterministic_keypairs;
use types::test_utils::TestRandom;
@ -878,7 +880,7 @@ where
let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot);
let (block, state, maybe_blob_sidecars) = self
let BeaconBlockResponseType::Full(block_response) = self
.chain
.produce_block_on_state(
state,
@ -887,14 +889,18 @@ where
randao_reveal,
Some(graffiti),
ProduceBlockVerification::VerifyRandao,
BlockProductionVersion::FullV2,
)
.await
.unwrap();
.unwrap()
else {
panic!("Should always be a full payload response");
};
let signed_block = block.sign(
let signed_block = block_response.block.sign(
&self.validator_keypairs[proposer_index].sk,
&state.fork(),
state.genesis_validators_root(),
&block_response.state.fork(),
block_response.state.genesis_validators_root(),
&self.spec,
);
@ -905,11 +911,13 @@ where
| SignedBeaconBlock::Capella(_) => (signed_block, None),
SignedBeaconBlock::Deneb(_) => (
signed_block,
maybe_blob_sidecars.map(|blobs| self.sign_blobs(blobs, &state, proposer_index)),
block_response
.maybe_side_car
.map(|blobs| self.sign_blobs(blobs, &block_response.state, proposer_index)),
),
};
(block_contents, state)
(block_contents, block_response.state)
}
/// Useful for the `per_block_processing` tests. Creates a block, and returns the state after
@ -938,7 +946,7 @@ where
let pre_state = state.clone();
let (block, state, maybe_blob_sidecars) = self
let BeaconBlockResponseType::Full(block_response) = self
.chain
.produce_block_on_state(
state,
@ -947,14 +955,18 @@ where
randao_reveal,
Some(graffiti),
ProduceBlockVerification::VerifyRandao,
BlockProductionVersion::FullV2,
)
.await
.unwrap();
.unwrap()
else {
panic!("Should always be a full payload response");
};
let signed_block = block.sign(
let signed_block = block_response.block.sign(
&self.validator_keypairs[proposer_index].sk,
&state.fork(),
state.genesis_validators_root(),
&block_response.state.fork(),
block_response.state.genesis_validators_root(),
&self.spec,
);
@ -964,14 +976,14 @@ where
| SignedBeaconBlock::Merge(_)
| SignedBeaconBlock::Capella(_) => (signed_block, None),
SignedBeaconBlock::Deneb(_) => {
if let Some(blobs) = maybe_blob_sidecars {
if let Some(blobs) = block_response.maybe_side_car {
let signed_blobs: SignedSidecarList<E, BlobSidecar<E>> = Vec::from(blobs)
.into_iter()
.map(|blob| {
blob.sign(
&self.validator_keypairs[proposer_index].sk,
&state.fork(),
state.genesis_validators_root(),
&block_response.state.fork(),
block_response.state.genesis_validators_root(),
&self.spec,
)
})
@ -990,7 +1002,6 @@ where
}
}
};
(block_contents, pre_state)
}

View File

@ -466,6 +466,11 @@ impl<T: EthSpec> From<GetPayloadResponse<T>>
}
}
pub enum GetPayloadResponseType<E: EthSpec> {
Full(GetPayloadResponse<E>),
Blinded(GetPayloadResponse<E>),
}
impl<T: EthSpec> GetPayloadResponse<T> {
pub fn execution_payload_ref(&self) -> ExecutionPayloadRef<T> {
self.to_ref().into()

View File

@ -42,13 +42,13 @@ use tokio_stream::wrappers::WatchStream;
use tree_hash::TreeHash;
use types::beacon_block_body::KzgCommitments;
use types::builder_bid::BuilderBid;
use types::payload::BlockProductionVersion;
use types::sidecar::{BlobItems, Sidecar};
use types::KzgProofs;
use types::{AbstractExecPayload, ExecutionPayloadDeneb, KzgProofs};
use types::{
AbstractExecPayload, BeaconStateError, BlindedPayload, BlockType, ChainSpec, Epoch,
ExecPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge,
BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadCapella,
ExecutionPayloadMerge, FullPayload, ProposerPreparationData, PublicKeyBytes, Signature, Slot,
};
use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot};
mod block_hash;
mod engine_api;
@ -87,9 +87,7 @@ pub enum ProvenancedPayload<P> {
Builder(P),
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> TryFrom<BuilderBid<E>>
for ProvenancedPayload<BlockProposalContents<E, Payload>>
{
impl<E: EthSpec> TryFrom<BuilderBid<E>> for ProvenancedPayload<BlockProposalContentsType<E>> {
type Error = Error;
fn try_from(value: BuilderBid<E>) -> Result<Self, Error> {
@ -112,12 +110,16 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> TryFrom<BuilderBid<E>>
.map_err(|_| Error::InvalidPayloadConversion)?,
block_value: builder_bid.value,
kzg_commitments: builder_bid.blinded_blobs_bundle.commitments,
blobs: BlobItems::try_from_blob_roots(builder_bid.blinded_blobs_bundle.blob_roots)
.map_err(Error::InvalidBlobConversion)?,
blobs: BlobItems::<E>::try_from_blob_roots(
builder_bid.blinded_blobs_bundle.blob_roots,
)
.map_err(Error::InvalidBlobConversion)?,
proofs: builder_bid.blinded_blobs_bundle.proofs,
},
};
Ok(ProvenancedPayload::Builder(block_proposal_contents))
Ok(ProvenancedPayload::Builder(
BlockProposalContentsType::Blinded(block_proposal_contents),
))
}
}
@ -145,6 +147,7 @@ pub enum Error {
InvalidPayloadConversion,
InvalidBlobConversion(String),
BeaconStateError(BeaconStateError),
PayloadTypeMismatch,
}
impl From<BeaconStateError> for Error {
@ -159,6 +162,11 @@ impl From<ApiError> for Error {
}
}
pub enum BlockProposalContentsType<E: EthSpec> {
Full(BlockProposalContents<E, FullPayload<E>>),
Blinded(BlockProposalContents<E, BlindedPayload<E>>),
}
pub enum BlockProposalContents<T: EthSpec, Payload: AbstractExecPayload<T>> {
Payload {
payload: Payload,
@ -173,6 +181,22 @@ pub enum BlockProposalContents<T: EthSpec, Payload: AbstractExecPayload<T>> {
},
}
impl<T: EthSpec> From<BlockProposalContents<T, FullPayload<T>>>
for BlockProposalContents<T, BlindedPayload<T>>
{
fn from(item: BlockProposalContents<T, FullPayload<T>>) -> Self {
let block_value = item.block_value().to_owned();
let blinded_payload: BlockProposalContents<T, BlindedPayload<T>> =
BlockProposalContents::Payload {
payload: item.to_payload().execution_payload().into(),
block_value,
};
blinded_payload
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> TryFrom<GetPayloadResponse<E>>
for BlockProposalContents<E, Payload>
{
@ -197,6 +221,17 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> TryFrom<GetPayloadResponse<E>>
}
}
impl<E: EthSpec> TryFrom<GetPayloadResponseType<E>> for BlockProposalContentsType<E> {
type Error = Error;
fn try_from(response_type: GetPayloadResponseType<E>) -> Result<Self, Error> {
match response_type {
GetPayloadResponseType::Full(response) => Ok(Self::Full(response.try_into()?)),
GetPayloadResponseType::Blinded(response) => Ok(Self::Blinded(response.try_into()?)),
}
}
}
#[allow(clippy::type_complexity)]
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Payload> {
pub fn deconstruct(
@ -206,19 +241,26 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
Option<KzgCommitments<T>>,
Option<<Payload::Sidecar as Sidecar<T>>::BlobItems>,
Option<KzgProofs<T>>,
Uint256,
) {
match self {
Self::Payload {
payload,
block_value: _,
} => (payload, None, None, None),
block_value,
} => (payload, None, None, None, block_value),
Self::PayloadAndBlobs {
payload,
block_value: _,
block_value,
kzg_commitments,
blobs,
proofs,
} => (payload, Some(kzg_commitments), Some(blobs), Some(proofs)),
} => (
payload,
Some(kzg_commitments),
Some(blobs),
Some(proofs),
block_value,
),
}
}
@ -795,7 +837,8 @@ impl<T: EthSpec> ExecutionLayer<T> {
///
/// The result will be returned from the first node that returns successfully. No more nodes
/// will be contacted.
pub async fn get_payload<Payload: AbstractExecPayload<T>>(
#[allow(clippy::too_many_arguments)]
pub async fn get_payload(
&self,
parent_hash: ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
@ -803,14 +846,11 @@ impl<T: EthSpec> ExecutionLayer<T> {
builder_params: BuilderParams,
current_fork: ForkName,
spec: &ChainSpec,
) -> Result<BlockProposalContents<T, Payload>, Error> {
let payload_result = match Payload::block_type() {
BlockType::Blinded => {
let _timer = metrics::start_timer_vec(
&metrics::EXECUTION_LAYER_REQUEST_TIMES,
&[metrics::GET_BLINDED_PAYLOAD],
);
self.get_blinded_payload(
block_production_version: BlockProductionVersion,
) -> Result<BlockProposalContentsType<T>, Error> {
let payload_result_type = match block_production_version {
BlockProductionVersion::V3 => match self
.determine_and_fetch_payload(
parent_hash,
payload_attributes,
forkchoice_update_params,
@ -819,27 +859,51 @@ impl<T: EthSpec> ExecutionLayer<T> {
spec,
)
.await
}
BlockType::Full => {
{
Ok(payload) => payload,
Err(e) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
&[metrics::FAILURE],
);
return Err(e);
}
},
BlockProductionVersion::BlindedV2 => {
let _timer = metrics::start_timer_vec(
&metrics::EXECUTION_LAYER_REQUEST_TIMES,
&[metrics::GET_PAYLOAD],
&[metrics::GET_BLINDED_PAYLOAD],
);
self.get_full_payload(
self.determine_and_fetch_payload(
parent_hash,
payload_attributes,
forkchoice_update_params,
builder_params,
current_fork,
spec,
)
.await?
}
BlockProductionVersion::FullV2 => self
.get_full_payload_with(
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
noop,
)
.await
.and_then(GetPayloadResponse::try_into)
.map(ProvenancedPayload::Local)
}
.and_then(GetPayloadResponseType::try_into)
.map(ProvenancedPayload::Local)?,
};
// Track some metrics and return the result.
match payload_result {
Ok(ProvenancedPayload::Local(block_proposal_contents)) => {
let block_proposal_content_type = match payload_result_type {
ProvenancedPayload::Local(local_payload) => local_payload,
ProvenancedPayload::Builder(builder_payload) => builder_payload,
};
match block_proposal_content_type {
BlockProposalContentsType::Full(block_proposal_contents) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
&[metrics::SUCCESS],
@ -848,9 +912,15 @@ impl<T: EthSpec> ExecutionLayer<T> {
&metrics::EXECUTION_LAYER_GET_PAYLOAD_SOURCE,
&[metrics::LOCAL],
);
Ok(block_proposal_contents)
if matches!(block_production_version, BlockProductionVersion::BlindedV2) {
Ok(BlockProposalContentsType::Blinded(
block_proposal_contents.into(),
))
} else {
Ok(BlockProposalContentsType::Full(block_proposal_contents))
}
}
Ok(ProvenancedPayload::Builder(block_proposal_contents)) => {
BlockProposalContentsType::Blinded(block_proposal_contents) => {
metrics::inc_counter_vec(
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
&[metrics::SUCCESS],
@ -859,19 +929,12 @@ impl<T: EthSpec> ExecutionLayer<T> {
&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)
Ok(BlockProposalContentsType::Blinded(block_proposal_contents))
}
}
}
async fn get_blinded_payload<Payload: AbstractExecPayload<T>>(
async fn determine_and_fetch_payload(
&self,
parent_hash: ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
@ -879,11 +942,10 @@ impl<T: EthSpec> ExecutionLayer<T> {
builder_params: BuilderParams,
current_fork: ForkName,
spec: &ChainSpec,
) -> Result<ProvenancedPayload<BlockProposalContents<T, Payload>>, Error> {
) -> Result<ProvenancedPayload<BlockProposalContentsType<T>>, Error> {
if let Some(builder) = self.builder() {
let slot = builder_params.slot;
let pubkey = builder_params.pubkey;
match builder_params.chain_health {
ChainHealth::Healthy => {
info!(
@ -895,7 +957,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
);
// 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!(
let ((relay_result, relay_duration), (local_result_type, local_duration)) = tokio::join!(
timed_future(metrics::GET_BLINDED_PAYLOAD_BUILDER, async {
builder
.get_builder_header::<T>(slot, parent_hash, &pubkey)
@ -912,6 +974,11 @@ impl<T: EthSpec> ExecutionLayer<T> {
})
);
let local_result = match local_result_type? {
GetPayloadResponseType::Full(payload) => Ok(payload),
GetPayloadResponseType::Blinded(_) => Err(Error::PayloadTypeMismatch),
};
info!(
self.log(),
"Requested blinded execution payload";
@ -939,7 +1006,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
"local_block_hash" => ?local.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local.try_into()?))
Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full(
local.try_into()?,
)))
}
(Ok(None), Ok(local)) => {
info!(
@ -949,7 +1018,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
"local_block_hash" => ?local.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local.try_into()?))
Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full(
local.try_into()?,
)))
}
(Ok(Some(relay)), Ok(local)) => {
let header = &relay.data.message.header();
@ -973,7 +1044,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
"local_block_value" => %local_value,
"relay_value" => %relay_value
);
return Ok(ProvenancedPayload::Local(local.try_into()?));
return Ok(ProvenancedPayload::Local(
BlockProposalContentsType::Full(local.try_into()?),
));
} else if local.should_override_builder().unwrap_or(false) {
let percentage_difference =
percentage_difference_u256(local_value, *relay_value);
@ -989,7 +1062,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
"local_block_value" => %local_value,
"relay_value" => %relay_value
);
return Ok(ProvenancedPayload::Local(local.try_into()?));
return Ok(ProvenancedPayload::Local(
BlockProposalContentsType::Full(local.try_into()?),
));
}
} else {
info!(
@ -1020,7 +1095,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
"relay_block_hash" => ?header.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local.try_into()?))
Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full(
local.try_into()?,
)))
}
Err(reason) => {
metrics::inc_counter_vec(
@ -1035,7 +1112,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
"relay_block_hash" => ?header.block_hash(),
"parent_hash" => ?parent_hash,
);
Ok(ProvenancedPayload::Local(local.try_into()?))
Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full(
local.try_into()?,
)))
}
}
}
@ -1132,28 +1211,10 @@ impl<T: EthSpec> ExecutionLayer<T> {
current_fork,
)
.await
.and_then(GetPayloadResponse::try_into)
.and_then(GetPayloadResponseType::try_into)
.map(ProvenancedPayload::Local)
}
/// Get a full payload without caching its result in the execution layer's payload cache.
async fn get_full_payload(
&self,
parent_hash: ExecutionBlockHash,
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
) -> Result<GetPayloadResponse<T>, Error> {
self.get_full_payload_with(
parent_hash,
payload_attributes,
forkchoice_update_params,
current_fork,
noop,
)
.await
}
/// Get a full payload and cache its result in the execution layer's payload cache.
async fn get_full_payload_caching(
&self,
@ -1161,7 +1222,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
payload_attributes: &PayloadAttributes,
forkchoice_update_params: ForkchoiceUpdateParameters,
current_fork: ForkName,
) -> Result<GetPayloadResponse<T>, Error> {
) -> Result<GetPayloadResponseType<T>, Error> {
self.get_full_payload_with(
parent_hash,
payload_attributes,
@ -1182,7 +1243,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
&ExecutionLayer<T>,
PayloadContentsRefTuple<T>,
) -> Option<FullPayloadContents<T>>,
) -> Result<GetPayloadResponse<T>, Error> {
) -> Result<GetPayloadResponseType<T>, Error> {
self.engine()
.request(move |engine| async move {
let payload_id = if let Some(id) = engine
@ -1244,6 +1305,10 @@ impl<T: EthSpec> ExecutionLayer<T> {
"timestamp" => payload_attributes.timestamp(),
"parent_hash" => ?parent_hash,
);
let _timer = metrics::start_timer_vec(
&metrics::EXECUTION_LAYER_REQUEST_TIMES,
&[metrics::GET_PAYLOAD],
);
engine.api.get_payload::<T>(current_fork, payload_id).await
}.await?;
@ -1268,7 +1333,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
);
}
Ok(payload_response)
Ok(GetPayloadResponseType::Full(payload_response))
})
.await
.map_err(Box::new)

View File

@ -508,11 +508,7 @@ pub fn serve<E: EthSpec>(
finalized_hash: Some(finalized_execution_hash),
};
let (payload, _block_value, maybe_blobs_bundle): (
ExecutionPayload<E>,
Uint256,
Option<BlobsBundle<E>>,
) = builder
let payload_response_type = builder
.el
.get_full_payload_caching(
head_execution_hash,
@ -521,38 +517,88 @@ pub fn serve<E: EthSpec>(
fork,
)
.await
.map_err(|_| reject("couldn't get payload"))?
.into();
.map_err(|_| reject("couldn't get payload"))?;
let mut message = match fork {
ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb {
header: payload
.as_deneb()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
blinded_blobs_bundle: maybe_blobs_bundle
.map(Into::into)
.unwrap_or_default(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Capella => BuilderBid::Capella(BuilderBidCapella {
header: payload
.as_capella()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Merge => BuilderBid::Merge(BuilderBidMerge {
header: payload
.as_merge()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Base | ForkName::Altair => return Err(reject("invalid fork")),
let mut message = match payload_response_type {
crate::GetPayloadResponseType::Full(payload_response) => {
let (payload, _block_value, maybe_blobs_bundle): (
ExecutionPayload<E>,
Uint256,
Option<BlobsBundle<E>>,
) = payload_response.into();
match fork {
ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb {
header: payload
.as_deneb()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
blinded_blobs_bundle: maybe_blobs_bundle
.map(Into::into)
.unwrap_or_default(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Capella => BuilderBid::Capella(BuilderBidCapella {
header: payload
.as_capella()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Merge => BuilderBid::Merge(BuilderBidMerge {
header: payload
.as_merge()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Base | ForkName::Altair => {
return Err(reject("invalid fork"))
}
}
}
crate::GetPayloadResponseType::Blinded(payload_response) => {
let (payload, _block_value, maybe_blobs_bundle): (
ExecutionPayload<E>,
Uint256,
Option<BlobsBundle<E>>,
) = payload_response.into();
match fork {
ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb {
header: payload
.as_deneb()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
blinded_blobs_bundle: maybe_blobs_bundle
.map(Into::into)
.unwrap_or_default(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Capella => BuilderBid::Capella(BuilderBidCapella {
header: payload
.as_capella()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Merge => BuilderBid::Merge(BuilderBidMerge {
header: payload
.as_merge()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Base | ForkName::Altair => {
return Err(reject("invalid fork"))
}
}
}
};
message.set_gas_limit(cached_data.gas_limit);

View File

@ -5,12 +5,12 @@ use crate::{
},
Config, *,
};
use keccak_hash::H256;
use kzg::Kzg;
use sensitive_url::SensitiveUrl;
use task_executor::TaskExecutor;
use tempfile::NamedTempFile;
use tree_hash::TreeHash;
use types::{Address, ChainSpec, Epoch, EthSpec, FullPayload, Hash256, MainnetEthSpec};
use types::{Address, ChainSpec, Epoch, EthSpec, Hash256, MainnetEthSpec};
pub struct MockExecutionLayer<T: EthSpec> {
pub server: MockServer<T>,
@ -133,20 +133,25 @@ impl<T: EthSpec> MockExecutionLayer<T> {
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, None);
let payload: ExecutionPayload<T> = self
let block_proposal_content_type = self
.el
.get_payload::<FullPayload<T>>(
.get_payload(
parent_hash,
&payload_attributes,
forkchoice_update_params,
builder_params,
ForkName::Merge,
&self.spec,
BlockProductionVersion::FullV2,
)
.await
.unwrap()
.to_payload()
.into();
.unwrap();
let payload: ExecutionPayload<T> = match block_proposal_content_type {
BlockProposalContentsType::Full(block) => block.to_payload().into(),
BlockProposalContentsType::Blinded(_) => panic!("Should always be a full payload"),
};
let block_hash = payload.block_hash();
assert_eq!(payload.parent_hash(), parent_hash);
@ -167,20 +172,64 @@ impl<T: EthSpec> MockExecutionLayer<T> {
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, None);
let payload_header = self
let block_proposal_content_type = self
.el
.get_payload::<BlindedPayload<T>>(
.get_payload(
parent_hash,
&payload_attributes,
forkchoice_update_params,
builder_params,
ForkName::Merge,
&self.spec,
BlockProductionVersion::BlindedV2,
)
.await
.unwrap()
.to_payload();
.unwrap();
match block_proposal_content_type {
BlockProposalContentsType::Full(block) => {
let payload_header = block.to_payload();
self.assert_valid_execution_payload_on_head(
payload,
payload_header,
block_hash,
parent_hash,
block_number,
timestamp,
prev_randao,
)
.await;
}
BlockProposalContentsType::Blinded(block) => {
let payload_header = block.to_payload();
self.assert_valid_execution_payload_on_head(
payload,
payload_header,
block_hash,
parent_hash,
block_number,
timestamp,
prev_randao,
)
.await;
}
};
self
}
#[allow(clippy::too_many_arguments)]
pub async fn assert_valid_execution_payload_on_head<Payload: AbstractExecPayload<T>>(
&self,
payload: ExecutionPayload<T>,
payload_header: Payload,
block_hash: ExecutionBlockHash,
parent_hash: ExecutionBlockHash,
block_number: u64,
timestamp: u64,
prev_randao: H256,
) {
assert_eq!(payload_header.block_hash(), block_hash);
assert_eq!(payload_header.parent_hash(), parent_hash);
assert_eq!(payload_header.block_number(), block_number);
@ -224,8 +273,6 @@ impl<T: EthSpec> MockExecutionLayer<T> {
assert_eq!(head_execution_block.block_number(), block_number);
assert_eq!(head_execution_block.block_hash(), block_hash);
assert_eq!(head_execution_block.parent_hash(), parent_hash);
self
}
pub fn move_to_block_prior_to_terminal_block(self) -> Self {

View File

@ -1,62 +1,51 @@
use beacon_chain::BlockProductionError;
use eth2::types::{BeaconBlockAndBlobSidecars, BlindedBeaconBlockAndBlobSidecars, BlockContents};
use types::{
BeaconBlock, BlindedBlobSidecarList, BlindedPayload, BlobSidecarList, EthSpec, ForkName,
FullPayload,
};
use types::{AbstractExecPayload, BeaconBlock, EthSpec, ForkName, SidecarList};
type Error = warp::reject::Rejection;
type FullBlockContents<E> = BlockContents<E, FullPayload<E>>;
type BlindedBlockContents<E> = BlockContents<E, BlindedPayload<E>>;
pub fn build_block_contents<E: EthSpec>(
pub fn build_block_contents<E: EthSpec, Payload: AbstractExecPayload<E>>(
fork_name: ForkName,
block: BeaconBlock<E, FullPayload<E>>,
maybe_blobs: Option<BlobSidecarList<E>>,
) -> Result<FullBlockContents<E>, Error> {
match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
Ok(BlockContents::Block(block))
}
ForkName::Deneb => {
if let Some(blob_sidecars) = maybe_blobs {
let block_and_blobs = BeaconBlockAndBlobSidecars {
block,
blob_sidecars,
};
Ok(BlockContents::BlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
BlockProductionError::MissingBlobs,
))
block: BeaconBlock<E, Payload>,
maybe_blobs: Option<SidecarList<E, <Payload as AbstractExecPayload<E>>::Sidecar>>,
) -> Result<BlockContents<E, Payload>, Error> {
match Payload::block_type() {
types::BlockType::Blinded => match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
Ok(BlockContents::Block(block))
}
}
}
}
pub fn build_blinded_block_contents<E: EthSpec>(
fork_name: ForkName,
block: BeaconBlock<E, BlindedPayload<E>>,
maybe_blobs: Option<BlindedBlobSidecarList<E>>,
) -> Result<BlindedBlockContents<E>, Error> {
match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
Ok(BlockContents::Block(block))
}
ForkName::Deneb => {
if let Some(blinded_blob_sidecars) = maybe_blobs {
let block_and_blobs = BlindedBeaconBlockAndBlobSidecars {
blinded_block: block,
blinded_blob_sidecars,
};
Ok(BlockContents::BlindedBlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
BlockProductionError::MissingBlobs,
))
}
}
ForkName::Deneb => {
if let Some(blinded_blob_sidecars) = maybe_blobs {
let block_and_blobs = BlindedBeaconBlockAndBlobSidecars {
blinded_block: block,
blinded_blob_sidecars,
};
Ok(BlockContents::BlindedBlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
BlockProductionError::MissingBlobs,
))
}
}
},
types::BlockType::Full => match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
Ok(BlockContents::Block(block))
}
ForkName::Deneb => {
if let Some(blob_sidecars) = maybe_blobs {
let block_and_blobs = BeaconBlockAndBlobSidecars {
block,
blob_sidecars,
};
Ok(BlockContents::BlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
BlockProductionError::MissingBlobs,
))
}
}
},
}
}

View File

@ -14,6 +14,7 @@ mod build_block_contents;
mod builder_states;
mod database;
mod metrics;
mod produce_block;
mod proposer_duties;
mod publish_blocks;
mod standard_block_rewards;
@ -27,10 +28,11 @@ mod validator;
mod validator_inclusion;
mod version;
use crate::produce_block::{produce_blinded_block_v2, produce_block_v2, produce_block_v3};
use beacon_chain::{
attestation_verification::VerifiedAttestation, observed_operations::ObservationOutcome,
validator_monitor::timestamp_now, AttestationError as AttnError, BeaconChain, BeaconChainError,
BeaconChainTypes, ProduceBlockVerification, WhenSlotSkipped,
BeaconChainTypes, WhenSlotSkipped,
};
use beacon_processor::BeaconProcessorSend;
pub use block_id::BlockId;
@ -39,8 +41,7 @@ use bytes::Bytes;
use directory::DEFAULT_ROOT_DIR;
use eth2::types::{
self as api_types, BroadcastValidation, EndpointVersion, ForkChoice, ForkChoiceNode,
SignedBlindedBlockContents, SignedBlockContents, SkipRandaoVerification, ValidatorId,
ValidatorStatus,
SignedBlindedBlockContents, SignedBlockContents, ValidatorId, ValidatorStatus,
};
use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage};
use lighthouse_version::version_with_platform;
@ -75,7 +76,7 @@ use tokio_stream::{
};
use types::{
Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError,
BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload,
BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName,
ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof,
SignedBlsToExecutionChange, SignedContributionAndProof, SignedValidatorRegistrationData,
SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData,
@ -83,7 +84,7 @@ use types::{
use validator::pubkey_to_validator_index;
use version::{
add_consensus_version_header, execution_optimistic_finalized_fork_versioned_response,
fork_versioned_response, inconsistent_fork_rejection, unsupported_version_rejection, V1, V2,
inconsistent_fork_rejection, unsupported_version_rejection, V1, V2, V3,
};
use warp::http::StatusCode;
use warp::sse::Event;
@ -3052,17 +3053,17 @@ pub fn serve<T: BeaconChainTypes>(
))
}))
.and(warp::path::end())
.and(warp::header::optional::<api_types::Accept>("accept"))
.and(not_while_syncing_filter.clone())
.and(warp::query::<api_types::ValidatorBlocksQuery>())
.and(warp::header::optional::<api_types::Accept>("accept"))
.and(task_spawner_filter.clone())
.and(chain_filter.clone())
.and(log_filter.clone())
.then(
|endpoint_version: EndpointVersion,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
accept_header: Option<api_types::Accept>,
query: api_types::ValidatorBlocksQuery,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
log: Logger| {
@ -3073,60 +3074,10 @@ pub fn serve<T: BeaconChainTypes>(
"slot" => slot
);
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification =
if query.skip_randao_verification == SkipRandaoVerification::Yes {
if !randao_reveal.is_infinity() {
return Err(warp_utils::reject::custom_bad_request(
"randao_reveal must be point-at-infinity if verification is skipped"
.into(),
));
}
ProduceBlockVerification::NoVerification
} else {
ProduceBlockVerification::VerifyRandao
};
let (block, _, maybe_blobs) = chain
.produce_block_with_verification::<FullPayload<T::EthSpec>>(
randao_reveal,
slot,
query.graffiti.map(Into::into),
randao_verification,
)
.await
.map_err(warp_utils::reject::block_production_error)?;
let fork_name = block
.to_ref()
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let block_contents =
build_block_contents::build_block_contents(fork_name, block, maybe_blobs)?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/octet-stream")
.body(block_contents.as_ssz_bytes().into())
.map(|res: Response<Bytes>| {
add_consensus_version_header(res, fork_name)
})
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"failed to create response: {}",
e
))
}),
_ => fork_versioned_response(endpoint_version, fork_name, block_contents)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name)),
if endpoint_version == V3 {
produce_block_v3(endpoint_version, accept_header, chain, slot, query).await
} else {
produce_block_v2(endpoint_version, accept_header, chain, slot, query).await
}
})
},
@ -3154,65 +3105,8 @@ pub fn serve<T: BeaconChainTypes>(
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>| {
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification =
if query.skip_randao_verification == SkipRandaoVerification::Yes {
if !randao_reveal.is_infinity() {
return Err(warp_utils::reject::custom_bad_request(
"randao_reveal must be point-at-infinity if verification is skipped"
.into()
));
}
ProduceBlockVerification::NoVerification
} else {
ProduceBlockVerification::VerifyRandao
};
let (block, _, maybe_blobs) = chain
.produce_block_with_verification::<BlindedPayload<T::EthSpec>>(
randao_reveal,
slot,
query.graffiti.map(Into::into),
randao_verification,
)
produce_blinded_block_v2(EndpointVersion(2), accept_header, chain, slot, query)
.await
.map_err(warp_utils::reject::block_production_error)?;
let fork_name = block
.to_ref()
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let block_contents = build_block_contents::build_blinded_block_contents(
fork_name,
block,
maybe_blobs,
)?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/octet-stream")
.body(block_contents.as_ssz_bytes().into())
.map(|res: Response<Bytes>| {
add_consensus_version_header(res, fork_name)
})
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"failed to create response: {}",
e
))
}),
// Pose as a V2 endpoint so we return the fork `version`.
_ => fork_versioned_response(V2, fork_name, block_contents)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name)),
}
})
},
);
@ -3740,7 +3634,6 @@ pub fn serve<T: BeaconChainTypes>(
.as_ref()
.ok_or(BeaconChainError::BuilderMissing)
.map_err(warp_utils::reject::beacon_chain_error)?;
builder
.post_builder_validators(&filtered_registration_data)
.await

View File

@ -0,0 +1,231 @@
use bytes::Bytes;
use std::sync::Arc;
use types::{payload::BlockProductionVersion, *};
use beacon_chain::{
BeaconBlockResponse, BeaconBlockResponseType, BeaconChain, BeaconChainTypes,
ProduceBlockVerification,
};
use eth2::types::{self as api_types, EndpointVersion, SkipRandaoVerification};
use ssz::Encode;
use warp::{
hyper::{Body, Response},
Reply,
};
use crate::{
build_block_contents,
version::{
add_consensus_block_value_header, add_consensus_version_header,
add_execution_payload_blinded_header, add_execution_payload_value_header,
fork_versioned_response, inconsistent_fork_rejection,
},
};
pub fn get_randao_verification(
query: &api_types::ValidatorBlocksQuery,
randao_reveal_infinity: bool,
) -> Result<ProduceBlockVerification, warp::Rejection> {
let randao_verification = if query.skip_randao_verification == SkipRandaoVerification::Yes {
if !randao_reveal_infinity {
return Err(warp_utils::reject::custom_bad_request(
"randao_reveal must be point-at-infinity if verification is skipped".into(),
));
}
ProduceBlockVerification::NoVerification
} else {
ProduceBlockVerification::VerifyRandao
};
Ok(randao_verification)
}
pub async fn produce_block_v3<T: BeaconChainTypes>(
endpoint_version: EndpointVersion,
accept_header: Option<api_types::Accept>,
chain: Arc<BeaconChain<T>>,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
) -> Result<Response<Body>, warp::Rejection> {
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?;
let block_response_type = chain
.produce_block_with_verification(
randao_reveal,
slot,
query.graffiti.map(Into::into),
randao_verification,
BlockProductionVersion::V3,
)
.await
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("failed to fetch a block: {:?}", e))
})?;
match block_response_type {
BeaconBlockResponseType::Full(block_response) => {
build_response_v3(chain, block_response, endpoint_version, accept_header)
}
BeaconBlockResponseType::Blinded(block_response) => {
build_response_v3(chain, block_response, endpoint_version, accept_header)
}
}
}
pub fn build_response_v3<T: BeaconChainTypes, E: EthSpec, Payload: AbstractExecPayload<E>>(
chain: Arc<BeaconChain<T>>,
block_response: BeaconBlockResponse<E, Payload>,
endpoint_version: EndpointVersion,
accept_header: Option<api_types::Accept>,
) -> Result<Response<Body>, warp::Rejection> {
let fork_name = block_response
.block
.to_ref()
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let block_contents = build_block_contents::build_block_contents(
fork_name,
block_response.block,
block_response.maybe_side_car,
)?;
let execution_payload_blinded = Payload::block_type() == BlockType::Blinded;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/ssz")
.body(block_contents.as_ssz_bytes().into())
.map(|res: Response<Body>| add_consensus_version_header(res, fork_name))
.map(|res| add_execution_payload_blinded_header(res, execution_payload_blinded))
.map(|res: Response<Body>| {
add_execution_payload_value_header(res, block_response.execution_payload_value)
})
.map(|res| add_consensus_block_value_header(res, block_response.consensus_block_value))
.map_err(|e| -> warp::Rejection {
warp_utils::reject::custom_server_error(format!("failed to create response: {}", e))
}),
_ => fork_versioned_response(endpoint_version, fork_name, block_contents)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name))
.map(|res| add_execution_payload_blinded_header(res, execution_payload_blinded))
.map(|res| {
add_execution_payload_value_header(res, block_response.execution_payload_value)
})
.map(|res| add_consensus_block_value_header(res, block_response.consensus_block_value)),
}
}
pub async fn produce_blinded_block_v2<T: BeaconChainTypes>(
endpoint_version: EndpointVersion,
accept_header: Option<api_types::Accept>,
chain: Arc<BeaconChain<T>>,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
) -> Result<Response<Body>, warp::Rejection> {
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?;
let block_response_type = chain
.produce_block_with_verification(
randao_reveal,
slot,
query.graffiti.map(Into::into),
randao_verification,
BlockProductionVersion::BlindedV2,
)
.await
.map_err(warp_utils::reject::block_production_error)?;
match block_response_type {
BeaconBlockResponseType::Full(block_response) => {
build_response_v2(chain, block_response, endpoint_version, accept_header)
}
BeaconBlockResponseType::Blinded(block_response) => {
build_response_v2(chain, block_response, endpoint_version, accept_header)
}
}
}
pub async fn produce_block_v2<T: BeaconChainTypes>(
endpoint_version: EndpointVersion,
accept_header: Option<api_types::Accept>,
chain: Arc<BeaconChain<T>>,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
) -> Result<Response<Body>, warp::Rejection> {
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?;
let block_response_type = chain
.produce_block_with_verification(
randao_reveal,
slot,
query.graffiti.map(Into::into),
randao_verification,
BlockProductionVersion::FullV2,
)
.await
.map_err(warp_utils::reject::block_production_error)?;
match block_response_type {
BeaconBlockResponseType::Full(block_response) => {
build_response_v2(chain, block_response, endpoint_version, accept_header)
}
BeaconBlockResponseType::Blinded(block_response) => {
build_response_v2(chain, block_response, endpoint_version, accept_header)
}
}
}
pub fn build_response_v2<T: BeaconChainTypes, E: EthSpec, Payload: AbstractExecPayload<E>>(
chain: Arc<BeaconChain<T>>,
block_response: BeaconBlockResponse<E, Payload>,
endpoint_version: EndpointVersion,
accept_header: Option<api_types::Accept>,
) -> Result<Response<Body>, warp::Rejection> {
let fork_name = block_response
.block
.to_ref()
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let block_contents = build_block_contents::build_block_contents(
fork_name,
block_response.block,
block_response.maybe_side_car,
)?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.header("Content-Type", "application/octet-stream")
.body(block_contents.as_ssz_bytes().into())
.map(|res: Response<Bytes>| add_consensus_version_header(res, fork_name))
.map_err(|e| {
warp_utils::reject::custom_server_error(format!("failed to create response: {}", e))
}),
_ => fork_versioned_response(endpoint_version, fork_name, block_contents)
.map(|response| warp::reply::json(&response).into_response())
.map(|res| add_consensus_version_header(res, fork_name)),
}
}

View File

@ -1,5 +1,5 @@
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes};
use types::*;
use types::{BeaconState, PublicKeyBytes};
/// Uses the `chain.validator_pubkey_cache` to resolve a pubkey to a validator
/// index and then ensures that the validator exists in the given `state`.

View File

@ -1,12 +1,16 @@
use crate::api_types::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse;
use crate::api_types::EndpointVersion;
use eth2::CONSENSUS_VERSION_HEADER;
use eth2::{
CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER, EXECUTION_PAYLOAD_BLINDED_HEADER,
EXECUTION_PAYLOAD_VALUE_HEADER,
};
use serde::Serialize;
use types::{ForkName, ForkVersionedResponse, InconsistentFork};
use types::{ForkName, ForkVersionedResponse, InconsistentFork, Uint256};
use warp::reply::{self, Reply, Response};
pub const V1: EndpointVersion = EndpointVersion(1);
pub const V2: EndpointVersion = EndpointVersion(2);
pub const V3: EndpointVersion = EndpointVersion(3);
pub fn fork_versioned_response<T: Serialize>(
endpoint_version: EndpointVersion,
@ -15,7 +19,7 @@ pub fn fork_versioned_response<T: Serialize>(
) -> Result<ForkVersionedResponse<T>, warp::reject::Rejection> {
let fork_name = if endpoint_version == V1 {
None
} else if endpoint_version == V2 {
} else if endpoint_version == V2 || endpoint_version == V3 {
Some(fork_name)
} else {
return Err(unsupported_version_rejection(endpoint_version));
@ -53,6 +57,45 @@ pub fn add_consensus_version_header<T: Reply>(reply: T, fork_name: ForkName) ->
reply::with_header(reply, CONSENSUS_VERSION_HEADER, fork_name.to_string()).into_response()
}
/// Add the `Eth-Execution-Payload-Blinded` header to a response.
pub fn add_execution_payload_blinded_header<T: Reply>(
reply: T,
execution_payload_blinded: bool,
) -> Response {
reply::with_header(
reply,
EXECUTION_PAYLOAD_BLINDED_HEADER,
execution_payload_blinded.to_string(),
)
.into_response()
}
/// Add the `Eth-Execution-Payload-Value` header to a response.
pub fn add_execution_payload_value_header<T: Reply>(
reply: T,
execution_payload_value: Option<Uint256>,
) -> Response {
reply::with_header(
reply,
EXECUTION_PAYLOAD_VALUE_HEADER,
execution_payload_value.unwrap_or_default().to_string(),
)
.into_response()
}
/// Add the `Eth-Consensus-Block-Value` header to a response.
pub fn add_consensus_block_value_header<T: Reply>(
reply: T,
consensus_payload_value: Option<u64>,
) -> Response {
reply::with_header(
reply,
CONSENSUS_BLOCK_VALUE_HEADER,
consensus_payload_value.unwrap_or_default().to_string(),
)
.into_response()
}
pub fn inconsistent_fork_rejection(error: InconsistentFork) -> warp::reject::Rejection {
warp_utils::reject::custom_server_error(format!("wrong fork: {:?}", error))
}

View File

@ -21,6 +21,8 @@ use types::{
MainnetEthSpec, MinimalEthSpec, ProposerPreparationData, Slot,
};
use eth2::types::ForkVersionedBeaconBlockType::{Blinded, Full};
type E = MainnetEthSpec;
// Test that the deposit_contract endpoint returns the correct chain_id and address.
@ -617,13 +619,18 @@ pub async fn proposer_boost_re_org_test(
let randao_reveal = harness
.sign_randao_reveal(&state_b, proposer_index, slot_c)
.into();
let unsigned_block_contents_c = tester
let unsigned_block_type = tester
.client
.get_validator_blocks(slot_c, &randao_reveal, None)
.get_validator_blocks_v3::<E>(slot_c, &randao_reveal, None)
.await
.unwrap()
.data;
let (unsigned_block_c, block_c_blobs) = unsigned_block_contents_c.deconstruct();
.unwrap();
let (unsigned_block_c, block_c_blobs) = match unsigned_block_type {
Full(unsigned_block_contents_c) => unsigned_block_contents_c.data.deconstruct(),
Blinded(_) => {
panic!("Should not be a blinded block");
}
};
let block_c = harness.sign_beacon_block(unsigned_block_c, &state_b);
if should_re_org {

View File

@ -3544,7 +3544,6 @@ impl ApiTester {
.cached_head()
.head_random()
.unwrap();
let (_, randao_reveal) = self.get_test_randao(slot, epoch).await;
let payload: BlindedPayload<E> = self
@ -4586,8 +4585,7 @@ impl ApiTester {
assert_eq!(withdrawal_response.finalized, Some(false));
assert_eq!(withdrawal_response.data, expected_withdrawals.to_vec());
}
Err(e) => {
println!("{:?}", e);
Err(_) => {
panic!("query failed incorrectly");
}
}

View File

@ -38,8 +38,12 @@ use store::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedRes
pub const V1: EndpointVersion = EndpointVersion(1);
pub const V2: EndpointVersion = EndpointVersion(2);
pub const V3: EndpointVersion = EndpointVersion(3);
pub const CONSENSUS_VERSION_HEADER: &str = "Eth-Consensus-Version";
pub const EXECUTION_PAYLOAD_BLINDED_HEADER: &str = "Eth-Execution-Payload-Blinded";
pub const EXECUTION_PAYLOAD_VALUE_HEADER: &str = "Eth-Execution-Payload-Value";
pub const CONSENSUS_BLOCK_VALUE_HEADER: &str = "Eth-Consensus-Block-Value";
#[derive(Debug)]
pub enum Error {
@ -1628,6 +1632,26 @@ impl BeaconNodeHttpClient {
.await
}
/// `GET v2/validator/blocks/{slot}`
pub async fn get_validator_blocks_modular<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<ForkVersionedResponse<BlockContents<T, Payload>>, Error> {
let path = self
.get_validator_blocks_path::<T, Payload>(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
)
.await?;
self.get(path).await
}
/// returns `GET v2/validator/blocks/{slot}` URL path
pub async fn get_validator_blocks_path<T: EthSpec, Payload: AbstractExecPayload<T>>(
&self,
@ -1660,24 +1684,70 @@ impl BeaconNodeHttpClient {
Ok(path)
}
/// `GET v2/validator/blocks/{slot}`
pub async fn get_validator_blocks_modular<T: EthSpec, Payload: AbstractExecPayload<T>>(
/// `GET v3/validator/blocks/{slot}`
pub async fn get_validator_blocks_v3<T: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<ForkVersionedBeaconBlockType<T>, Error> {
self.get_validator_blocks_v3_modular(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
)
.await
}
/// `GET v3/validator/blocks/{slot}`
pub async fn get_validator_blocks_v3_modular<T: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<ForkVersionedResponse<BlockContents<T, Payload>>, Error> {
let path = self
.get_validator_blocks_path::<T, Payload>(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
)
.await?;
) -> Result<ForkVersionedBeaconBlockType<T>, Error> {
let mut path = self.eth_path(V3)?;
self.get(path).await
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("blocks")
.push(&slot.to_string());
path.query_pairs_mut()
.append_pair("randao_reveal", &randao_reveal.to_string());
if let Some(graffiti) = graffiti {
path.query_pairs_mut()
.append_pair("graffiti", &graffiti.to_string());
}
if skip_randao_verification == SkipRandaoVerification::Yes {
path.query_pairs_mut()
.append_pair("skip_randao_verification", "");
}
let response = self.get_response(path, |b| b).await?;
let is_blinded_payload = response
.headers()
.get(EXECUTION_PAYLOAD_BLINDED_HEADER)
.map(|value| value.to_str().unwrap_or_default().to_lowercase() == "true")
.unwrap_or(false);
if is_blinded_payload {
let blinded_payload = response
.json::<ForkVersionedResponse<BlockContents<T, BlindedPayload<T>>>>()
.await?;
Ok(ForkVersionedBeaconBlockType::Blinded(blinded_payload))
} else {
let full_payload = response
.json::<ForkVersionedResponse<BlockContents<T, FullPayload<T>>>>()
.await?;
Ok(ForkVersionedBeaconBlockType::Full(full_payload))
}
}
/// `GET v2/validator/blocks/{slot}` in ssz format

View File

@ -1384,6 +1384,11 @@ pub mod serde_status_code {
}
}
pub enum ForkVersionedBeaconBlockType<T: EthSpec> {
Full(ForkVersionedResponse<BlockContents<T, FullPayload<T>>>),
Blinded(ForkVersionedResponse<BlockContents<T, BlindedPayload<T>>>),
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -979,3 +979,10 @@ impl<T: EthSpec> From<BlindedPayload<T>> for ExecutionPayloadHeader<T> {
}
}
}
/// The block production flow version to be used.
pub enum BlockProductionVersion {
V3,
BlindedV2,
FullV2,
}

View File

@ -4,7 +4,8 @@ use crate::execution_engine::{
use crate::transactions::transactions;
use ethers_providers::Middleware;
use execution_layer::{
BuilderParams, ChainHealth, ExecutionLayer, PayloadAttributes, PayloadStatus,
BlockProposalContentsType, BuilderParams, ChainHealth, ExecutionLayer, PayloadAttributes,
PayloadStatus,
};
use fork_choice::ForkchoiceUpdateParameters;
use reqwest::{header::CONTENT_TYPE, Client};
@ -14,9 +15,10 @@ use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use task_executor::TaskExecutor;
use tokio::time::sleep;
use types::payload::BlockProductionVersion;
use types::{
Address, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
ForkName, FullPayload, Hash256, MainnetEthSpec, PublicKeyBytes, Slot, Uint256,
ForkName, Hash256, MainnetEthSpec, PublicKeyBytes, Slot, Uint256,
};
const EXECUTION_ENGINE_START_TIMEOUT: Duration = Duration::from_secs(60);
@ -322,21 +324,26 @@ impl<E: GenericExecutionEngine> TestRig<E> {
Some(vec![]),
None,
);
let valid_payload = self
let block_proposal_content_type = self
.ee_a
.execution_layer
.get_payload::<FullPayload<MainnetEthSpec>>(
.get_payload(
parent_hash,
&payload_attributes,
forkchoice_update_params,
builder_params,
TEST_FORK,
&self.spec,
BlockProductionVersion::FullV2,
)
.await
.unwrap()
.to_payload()
.execution_payload();
.unwrap();
let valid_payload = match block_proposal_content_type {
BlockProposalContentsType::Full(block) => block.to_payload().execution_payload(),
BlockProposalContentsType::Blinded(_) => panic!("Should always be a full payload"),
};
assert_eq!(valid_payload.transactions().len(), pending_txs.len());
/*
@ -468,21 +475,25 @@ impl<E: GenericExecutionEngine> TestRig<E> {
Some(vec![]),
None,
);
let second_payload = self
let block_proposal_content_type = self
.ee_a
.execution_layer
.get_payload::<FullPayload<MainnetEthSpec>>(
.get_payload(
parent_hash,
&payload_attributes,
forkchoice_update_params,
builder_params,
TEST_FORK,
&self.spec,
BlockProductionVersion::FullV2,
)
.await
.unwrap()
.to_payload()
.execution_payload();
.unwrap();
let second_payload = match block_proposal_content_type {
BlockProposalContentsType::Full(block) => block.to_payload().execution_payload(),
BlockProposalContentsType::Blinded(_) => panic!("Should always be a full payload"),
};
/*
* Execution Engine A: