diff --git a/beacon_node/beacon_chain/src/beacon_block_reward.rs b/beacon_node/beacon_chain/src/beacon_block_reward.rs index 8cbeae371..d05f7cb4f 100644 --- a/beacon_node/beacon_chain/src/beacon_block_reward.rs +++ b/beacon_node/beacon_chain/src/beacon_block_reward.rs @@ -33,6 +33,17 @@ impl BeaconChain { 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>( + &self, + block: BeaconBlockRef<'_, T::EthSpec, Payload>, + block_root: Hash256, + state: &BeaconState, + ) -> Result { let proposer_index = block.proposer_index(); let sync_aggregate_reward = @@ -178,7 +189,7 @@ impl BeaconChain { >( &self, block: BeaconBlockRef<'_, T::EthSpec, Payload>, - state: &mut BeaconState, + state: &BeaconState, ) -> Result { let total_active_balance = state.get_total_active_balance()?; let base_reward_per_increment = @@ -191,6 +202,9 @@ impl BeaconChain { .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 BeaconChain { )?; 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))?; diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9f5973393..f2378b4f9 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -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> { +struct PartialBeaconBlock { state: BeaconState, slot: Slot, proposer_index: u64, @@ -335,7 +335,7 @@ struct PartialBeaconBlock> { deposits: Vec, voluntary_exits: Vec, sync_aggregate: Option>, - prepare_payload_handle: Option>, + prepare_payload_handle: Option>, bls_to_execution_changes: Vec, } @@ -484,11 +484,18 @@ pub struct BeaconChain { pub kzg: Option>, } -type BeaconBlockAndState = ( - BeaconBlock, - BeaconState, - Option>::Sidecar>>, -); +pub enum BeaconBlockResponseType { + Full(BeaconBlockResponse>), + Blinded(BeaconBlockResponse>), +} + +pub struct BeaconBlockResponse> { + pub block: BeaconBlock, + pub state: BeaconState, + pub maybe_side_car: Option>::Sidecar>>, + pub execution_payload_value: Option, + pub consensus_block_value: Option, +} impl FinalizationAndCanonicity { pub fn is_finalized(self) -> bool { @@ -3949,38 +3956,16 @@ impl BeaconChain { 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 + 'static>( - self: &Arc, - randao_reveal: Signature, - slot: Slot, - validator_graffiti: Option, - ) -> Result, 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 + 'static, - >( + pub async fn produce_block_with_verification( self: &Arc, randao_reveal: Signature, slot: Slot, validator_graffiti: Option, verification: ProduceBlockVerification, - ) -> Result, BlockProductionError> { + block_production_version: BlockProductionVersion, + ) -> Result, 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 BeaconChain { // Part 2/2 (async, with some blocking components) // // Produce the block upon the state - self.produce_block_on_state::( + self.produce_block_on_state( state, state_root_opt, slot, randao_reveal, validator_graffiti, verification, + block_production_version, ) .await } @@ -4568,7 +4554,8 @@ impl BeaconChain { /// 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 + 'static>( + #[allow(clippy::too_many_arguments)] + pub async fn produce_block_on_state( self: &Arc, state: BeaconState, state_root_opt: Option, @@ -4576,7 +4563,8 @@ impl BeaconChain { randao_reveal: Signature, validator_graffiti: Option, verification: ProduceBlockVerification, - ) -> Result, BlockProductionError> { + block_production_version: BlockProductionVersion, + ) -> Result, BlockProductionError> { // Part 1/3 (blocking) // // Perform the state advance and block-packing functions. @@ -4591,6 +4579,7 @@ impl BeaconChain { produce_at_slot, randao_reveal, validator_graffiti, + block_production_version, ) }, "produce_partial_beacon_block", @@ -4598,50 +4587,96 @@ impl BeaconChain { .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 + 'static>( + fn produce_partial_beacon_block( self: &Arc, mut state: BeaconState, state_root_opt: Option, produce_at_slot: Slot, randao_reveal: Signature, validator_graffiti: Option, - ) -> Result, BlockProductionError> { + block_production_version: BlockProductionVersion, + ) -> Result, BlockProductionError> { let eth1_chain = self .eth1_chain .as_ref() @@ -4701,6 +4736,7 @@ impl BeaconChain { parent_root, proposer_index, builder_params, + block_production_version, )?; Some(prepare_payload_handle) } @@ -4710,6 +4746,7 @@ impl BeaconChain { 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, ð1_data, &self.spec)?; let bls_to_execution_changes = self @@ -4880,10 +4917,10 @@ impl BeaconChain { fn complete_partial_beacon_block>( &self, - partial_beacon_block: PartialBeaconBlock, + partial_beacon_block: PartialBeaconBlock, block_contents: Option>, verification: ProduceBlockVerification, - ) -> Result, BlockProductionError> { + ) -> Result, BlockProductionError> { let PartialBeaconBlock { mut state, slot, @@ -4905,7 +4942,7 @@ impl BeaconChain { 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 BeaconChain { }), None, None, + Uint256::zero(), ), BeaconState::Altair(_) => ( BeaconBlock::Altair(BeaconBlockAltair { @@ -4949,11 +4987,12 @@ impl BeaconChain { }), 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 BeaconChain { 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 BeaconChain { 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 BeaconChain { }), 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 BeaconChain { }), blobs, proofs, + execution_payload_value, ) } }; @@ -5057,7 +5104,6 @@ impl BeaconChain { 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 BeaconChain { // 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 BeaconChain { "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 diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 33c97efd2..093255b20 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -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 = - Result, BlockProductionError>; -pub type PreparePayloadHandle = JoinHandle>>; +pub type PreparePayloadResult = Result, BlockProductionError>; +pub type PreparePayloadHandle = JoinHandle>>; #[derive(PartialEq)] pub enum AllowOptimisticImport { @@ -398,16 +399,14 @@ pub fn validate_execution_payload_for_gossip( /// 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 + 'static, ->( +pub fn get_execution_payload( chain: Arc>, state: &BeaconState, parent_block_root: Hash256, proposer_index: u64, builder_params: BuilderParams, -) -> Result, BlockProductionError> { + block_production_version: BlockProductionVersion, +) -> Result, 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::( + prepare_execution_payload::( &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( +pub async fn prepare_execution_payload( chain: &Arc>, is_merge_transition_complete: bool, timestamp: u64, @@ -485,10 +485,10 @@ pub async fn prepare_execution_payload( builder_params: BuilderParams, withdrawals: Option>, parent_beacon_block_root: Option, -) -> Result, BlockProductionError> + block_production_version: BlockProductionVersion, +) -> Result, BlockProductionError> where T: BeaconChainTypes, - Payload: AbstractExecPayload, { 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::( + .get_payload( parent_hash, &payload_attributes, forkchoice_update_params, builder_params, fork, &chain.spec, + block_production_version, ) .await .map_err(BlockProductionError::GetPayloadFailed)?; diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 4efc776b2..e2d37078a 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -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; diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 7ef984254..333318f52 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -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> = 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) } diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 683e39a50..19b9a58eb 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -466,6 +466,11 @@ impl From> } } +pub enum GetPayloadResponseType { + Full(GetPayloadResponse), + Blinded(GetPayloadResponse), +} + impl GetPayloadResponse { pub fn execution_payload_ref(&self) -> ExecutionPayloadRef { self.to_ref().into() diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 623965ada..3f75d0042 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -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

{ Builder(P), } -impl> TryFrom> - for ProvenancedPayload> -{ +impl TryFrom> for ProvenancedPayload> { type Error = Error; fn try_from(value: BuilderBid) -> Result { @@ -112,12 +110,16 @@ impl> TryFrom> .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::::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 for Error { @@ -159,6 +162,11 @@ impl From for Error { } } +pub enum BlockProposalContentsType { + Full(BlockProposalContents>), + Blinded(BlockProposalContents>), +} + pub enum BlockProposalContents> { Payload { payload: Payload, @@ -173,6 +181,22 @@ pub enum BlockProposalContents> { }, } +impl From>> + for BlockProposalContents> +{ + fn from(item: BlockProposalContents>) -> Self { + let block_value = item.block_value().to_owned(); + + let blinded_payload: BlockProposalContents> = + BlockProposalContents::Payload { + payload: item.to_payload().execution_payload().into(), + block_value, + }; + + blinded_payload + } +} + impl> TryFrom> for BlockProposalContents { @@ -197,6 +221,17 @@ impl> TryFrom> } } +impl TryFrom> for BlockProposalContentsType { + type Error = Error; + + fn try_from(response_type: GetPayloadResponseType) -> Result { + 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> BlockProposalContents { pub fn deconstruct( @@ -206,19 +241,26 @@ impl> BlockProposalContents>, Option<>::BlobItems>, Option>, + 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 ExecutionLayer { /// /// The result will be returned from the first node that returns successfully. No more nodes /// will be contacted. - pub async fn get_payload>( + #[allow(clippy::too_many_arguments)] + pub async fn get_payload( &self, parent_hash: ExecutionBlockHash, payload_attributes: &PayloadAttributes, @@ -803,14 +846,11 @@ impl ExecutionLayer { builder_params: BuilderParams, current_fork: ForkName, spec: &ChainSpec, - ) -> Result, 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, 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 ExecutionLayer { 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 ExecutionLayer { &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 ExecutionLayer { &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>( + async fn determine_and_fetch_payload( &self, parent_hash: ExecutionBlockHash, payload_attributes: &PayloadAttributes, @@ -879,11 +942,10 @@ impl ExecutionLayer { builder_params: BuilderParams, current_fork: ForkName, spec: &ChainSpec, - ) -> Result>, Error> { + ) -> Result>, 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 ExecutionLayer { ); // 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::(slot, parent_hash, &pubkey) @@ -912,6 +974,11 @@ impl ExecutionLayer { }) ); + 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 ExecutionLayer { "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 ExecutionLayer { "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 ExecutionLayer { "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 ExecutionLayer { "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 ExecutionLayer { "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 ExecutionLayer { "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 ExecutionLayer { 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, 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 ExecutionLayer { payload_attributes: &PayloadAttributes, forkchoice_update_params: ForkchoiceUpdateParameters, current_fork: ForkName, - ) -> Result, Error> { + ) -> Result, Error> { self.get_full_payload_with( parent_hash, payload_attributes, @@ -1182,7 +1243,7 @@ impl ExecutionLayer { &ExecutionLayer, PayloadContentsRefTuple, ) -> Option>, - ) -> Result, Error> { + ) -> Result, Error> { self.engine() .request(move |engine| async move { let payload_id = if let Some(id) = engine @@ -1244,6 +1305,10 @@ impl ExecutionLayer { "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::(current_fork, payload_id).await }.await?; @@ -1268,7 +1333,7 @@ impl ExecutionLayer { ); } - Ok(payload_response) + Ok(GetPayloadResponseType::Full(payload_response)) }) .await .map_err(Box::new) diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index 1e45ef272..32b352b6a 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -508,11 +508,7 @@ pub fn serve( finalized_hash: Some(finalized_execution_hash), }; - let (payload, _block_value, maybe_blobs_bundle): ( - ExecutionPayload, - Uint256, - Option>, - ) = builder + let payload_response_type = builder .el .get_full_payload_caching( head_execution_hash, @@ -521,38 +517,88 @@ pub fn serve( 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, + Uint256, + Option>, + ) = 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, + Uint256, + Option>, + ) = 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); diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index 671742843..72f0388e2 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -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 { pub server: MockServer, @@ -133,20 +133,25 @@ impl MockExecutionLayer { 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 = self + + let block_proposal_content_type = self .el - .get_payload::>( + .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 = 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 MockExecutionLayer { 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::>( + .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>( + &self, + payload: ExecutionPayload, + 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 MockExecutionLayer { 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 { diff --git a/beacon_node/http_api/src/build_block_contents.rs b/beacon_node/http_api/src/build_block_contents.rs index c8f28fa9a..f59a4b521 100644 --- a/beacon_node/http_api/src/build_block_contents.rs +++ b/beacon_node/http_api/src/build_block_contents.rs @@ -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 = BlockContents>; -type BlindedBlockContents = BlockContents>; -pub fn build_block_contents( +pub fn build_block_contents>( fork_name: ForkName, - block: BeaconBlock>, - maybe_blobs: Option>, -) -> Result, 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, + maybe_blobs: Option>::Sidecar>>, +) -> Result, 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( - fork_name: ForkName, - block: BeaconBlock>, - maybe_blobs: Option>, -) -> Result, 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, + )) + } + } + }, } } diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index a80765518..309db204a 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -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( )) })) .and(warp::path::end()) + .and(warp::header::optional::("accept")) .and(not_while_syncing_filter.clone()) .and(warp::query::()) - .and(warp::header::optional::("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, + query: api_types::ValidatorBlocksQuery, task_spawner: TaskSpawner, chain: Arc>, log: Logger| { @@ -3073,60 +3074,10 @@ pub fn serve( "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::>( - 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| { - 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( task_spawner: TaskSpawner, chain: Arc>| { 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::>( - 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| { - 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( .as_ref() .ok_or(BeaconChainError::BuilderMissing) .map_err(warp_utils::reject::beacon_chain_error)?; - builder .post_builder_validators(&filtered_registration_data) .await diff --git a/beacon_node/http_api/src/produce_block.rs b/beacon_node/http_api/src/produce_block.rs new file mode 100644 index 000000000..73da4853e --- /dev/null +++ b/beacon_node/http_api/src/produce_block.rs @@ -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 { + 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( + endpoint_version: EndpointVersion, + accept_header: Option, + chain: Arc>, + slot: Slot, + query: api_types::ValidatorBlocksQuery, +) -> Result, 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>( + chain: Arc>, + block_response: BeaconBlockResponse, + endpoint_version: EndpointVersion, + accept_header: Option, +) -> Result, 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| add_consensus_version_header(res, fork_name)) + .map(|res| add_execution_payload_blinded_header(res, execution_payload_blinded)) + .map(|res: Response| { + 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( + endpoint_version: EndpointVersion, + accept_header: Option, + chain: Arc>, + slot: Slot, + query: api_types::ValidatorBlocksQuery, +) -> Result, 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( + endpoint_version: EndpointVersion, + accept_header: Option, + chain: Arc>, + slot: Slot, + query: api_types::ValidatorBlocksQuery, +) -> Result, 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>( + chain: Arc>, + block_response: BeaconBlockResponse, + endpoint_version: EndpointVersion, + accept_header: Option, +) -> Result, 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| 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)), + } +} diff --git a/beacon_node/http_api/src/validator.rs b/beacon_node/http_api/src/validator.rs index 18e9dbf63..7f11ddd8f 100644 --- a/beacon_node/http_api/src/validator.rs +++ b/beacon_node/http_api/src/validator.rs @@ -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`. diff --git a/beacon_node/http_api/src/version.rs b/beacon_node/http_api/src/version.rs index e01ff9822..7b0690124 100644 --- a/beacon_node/http_api/src/version.rs +++ b/beacon_node/http_api/src/version.rs @@ -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( endpoint_version: EndpointVersion, @@ -15,7 +19,7 @@ pub fn fork_versioned_response( ) -> Result, 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(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( + 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( + reply: T, + execution_payload_value: Option, +) -> 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( + reply: T, + consensus_payload_value: Option, +) -> 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)) } diff --git a/beacon_node/http_api/tests/interactive_tests.rs b/beacon_node/http_api/tests/interactive_tests.rs index 17213d6f5..327215209 100644 --- a/beacon_node/http_api/tests/interactive_tests.rs +++ b/beacon_node/http_api/tests/interactive_tests.rs @@ -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::(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 { diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 82a3fe6ee..d532859c7 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -3544,7 +3544,6 @@ impl ApiTester { .cached_head() .head_random() .unwrap(); - let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; let payload: BlindedPayload = 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"); } } diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 2a3e3242c..c59e59abf 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -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>( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + skip_randao_verification: SkipRandaoVerification, + ) -> Result>, Error> { + let path = self + .get_validator_blocks_path::( + 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>( &self, @@ -1660,24 +1684,70 @@ impl BeaconNodeHttpClient { Ok(path) } - /// `GET v2/validator/blocks/{slot}` - pub async fn get_validator_blocks_modular>( + /// `GET v3/validator/blocks/{slot}` + pub async fn get_validator_blocks_v3( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + ) -> Result, 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( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, - ) -> Result>, Error> { - let path = self - .get_validator_blocks_path::( - slot, - randao_reveal, - graffiti, - skip_randao_verification, - ) - .await?; + ) -> Result, 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::>>>() + .await?; + Ok(ForkVersionedBeaconBlockType::Blinded(blinded_payload)) + } else { + let full_payload = response + .json::>>>() + .await?; + Ok(ForkVersionedBeaconBlockType::Full(full_payload)) + } } /// `GET v2/validator/blocks/{slot}` in ssz format diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index e87f254f1..c2d85a31d 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1384,6 +1384,11 @@ pub mod serde_status_code { } } +pub enum ForkVersionedBeaconBlockType { + Full(ForkVersionedResponse>>), + Blinded(ForkVersionedResponse>>), +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/payload.rs b/consensus/types/src/payload.rs index ff7caffcf..6d584fc1e 100644 --- a/consensus/types/src/payload.rs +++ b/consensus/types/src/payload.rs @@ -979,3 +979,10 @@ impl From> for ExecutionPayloadHeader { } } } + +/// The block production flow version to be used. +pub enum BlockProductionVersion { + V3, + BlindedV2, + FullV2, +} diff --git a/testing/execution_engine_integration/src/test_rig.rs b/testing/execution_engine_integration/src/test_rig.rs index 48195f871..42667f27c 100644 --- a/testing/execution_engine_integration/src/test_rig.rs +++ b/testing/execution_engine_integration/src/test_rig.rs @@ -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 TestRig { Some(vec![]), None, ); - let valid_payload = self + let block_proposal_content_type = self .ee_a .execution_layer - .get_payload::>( + .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 TestRig { Some(vec![]), None, ); - let second_payload = self + let block_proposal_content_type = self .ee_a .execution_layer - .get_payload::>( + .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: