From ae2b1d3d85265ac475510abaf7d528ffdff2b837 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 5 Sep 2023 18:27:35 +0300 Subject: [PATCH] blinded payload test case passing --- beacon_node/execution_layer/src/lib.rs | 285 ++++++++++++++++++++++++- beacon_node/http_api/src/lib.rs | 41 +--- beacon_node/http_api/src/validator.rs | 15 +- 3 files changed, 294 insertions(+), 47 deletions(-) diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 75c8ea9ee18..b2546baef8c 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -655,7 +655,7 @@ impl ExecutionLayer { block_production_version: BlockProductionVersion, ) -> Result, Error> { let payload_result_type = match block_production_version { - BlockProductionVersion::V3 | BlockProductionVersion::BlindedV2 => match self + BlockProductionVersion::V3 => match self .determine_and_fetch_payload( parent_hash, payload_attributes, @@ -675,6 +675,21 @@ impl ExecutionLayer { return Err(e); } }, + BlockProductionVersion::BlindedV2 => { + let _timer = metrics::start_timer_vec( + &metrics::EXECUTION_LAYER_REQUEST_TIMES, + &[metrics::GET_BLINDED_PAYLOAD], + ); + self.get_blinded_payload_v2( + parent_hash, + payload_attributes, + forkchoice_update_params, + builder_params, + current_fork, + spec, + ) + .await? + }, BlockProductionVersion::FullV2 => self .get_full_payload_with_v3( parent_hash, @@ -738,7 +753,6 @@ impl ExecutionLayer { ) -> Result, Error> { let payload_result = match Payload::block_type() { BlockType::Blinded => { - println!("BLOCK TYPE IS BLINDED"); let _timer = metrics::start_timer_vec( &metrics::EXECUTION_LAYER_REQUEST_TIMES, &[metrics::GET_BLINDED_PAYLOAD], @@ -754,7 +768,6 @@ impl ExecutionLayer { .await } BlockType::Full => { - println!("BLOCK TYPE IS FULL"); let _timer = metrics::start_timer_vec( &metrics::EXECUTION_LAYER_REQUEST_TIMES, &[metrics::GET_PAYLOAD], @@ -1085,6 +1098,272 @@ impl ExecutionLayer { .map(ProvenancedPayload::Local) } + async fn get_blinded_payload_v2( + &self, + parent_hash: ExecutionBlockHash, + payload_attributes: &PayloadAttributes, + forkchoice_update_params: ForkchoiceUpdateParameters, + builder_params: BuilderParams, + current_fork: ForkName, + spec: &ChainSpec, + ) -> 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!( + self.log(), + "Requesting blinded header from connected builder"; + "slot" => ?slot, + "pubkey" => ?pubkey, + "parent_hash" => ?parent_hash, + ); + + // 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!( + timed_future(metrics::GET_BLINDED_PAYLOAD_BUILDER, async { + builder + .get_builder_header::>(slot, parent_hash, &pubkey) + .await + }), + timed_future(metrics::GET_BLINDED_PAYLOAD_LOCAL, async { + self.get_full_payload_caching::>( + parent_hash, + payload_attributes, + forkchoice_update_params, + current_fork, + ) + .await + }) + ); + + info!( + self.log(), + "Requested blinded execution payload"; + "relay_fee_recipient" => match &relay_result { + Ok(Some(r)) => format!("{:?}", r.data.message.header.fee_recipient()), + Ok(None) => "empty response".to_string(), + Err(_) => "request failed".to_string(), + }, + "relay_response_ms" => relay_duration.as_millis(), + "local_fee_recipient" => match &local_result { + Ok(proposal_contents) => format!("{:?}", proposal_contents.payload().fee_recipient()), + Err(_) => "request failed".to_string() + }, + "local_response_ms" => local_duration.as_millis(), + "parent_hash" => ?parent_hash, + ); + + return match (relay_result, local_result) { + (Err(e), Ok(local)) => { + warn!( + self.log(), + "Builder error when requesting payload"; + "info" => "falling back to local execution client", + "relay_error" => ?e, + "local_block_hash" => ?local.payload().block_hash(), + "parent_hash" => ?parent_hash, + ); + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Blinded(local))) + } + (Ok(None), Ok(local)) => { + info!( + self.log(), + "Builder did not return a payload"; + "info" => "falling back to local execution client", + "local_block_hash" => ?local.payload().block_hash(), + "parent_hash" => ?parent_hash, + ); + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Blinded(local))) + } + (Ok(Some(relay)), Ok(local)) => { + let header = &relay.data.message.header; + + info!( + self.log(), + "Received local and builder payloads"; + "relay_block_hash" => ?header.block_hash(), + "local_block_hash" => ?local.payload().block_hash(), + "parent_hash" => ?parent_hash, + ); + + let relay_value = relay.data.message.value; + let local_value = *local.block_value(); + if !self.inner.always_prefer_builder_payload { + if local_value >= relay_value { + info!( + self.log(), + "Local block is more profitable than relay block"; + "local_block_value" => %local_value, + "relay_value" => %relay_value + ); + return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Blinded(local))); + } else { + info!( + self.log(), + "Relay block is more profitable than local block"; + "local_block_value" => %local_value, + "relay_value" => %relay_value + ); + } + } + + match verify_builder_bid( + &relay, + parent_hash, + payload_attributes, + Some(local.payload().block_number()), + self.inner.builder_profit_threshold, + current_fork, + spec, + ) { + Ok(()) => Ok(ProvenancedPayload::Builder( + BlockProposalContentsType::Blinded( + BlockProposalContents::Payload { + payload: relay.data.message.header, + block_value: relay.data.message.value, + _phantom: PhantomData, + }), + )), + Err(reason) if !reason.payload_invalid() => { + info!( + self.log(), + "Builder payload ignored"; + "info" => "using local payload", + "reason" => %reason, + "relay_block_hash" => ?header.block_hash(), + "parent_hash" => ?parent_hash, + ); + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Blinded(local))) + } + Err(reason) => { + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS, + &[reason.as_ref().as_ref()], + ); + warn!( + self.log(), + "Builder returned invalid payload"; + "info" => "using local payload", + "reason" => %reason, + "relay_block_hash" => ?header.block_hash(), + "parent_hash" => ?parent_hash, + ); + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Blinded(local))) + } + } + } + (Ok(Some(relay)), Err(local_error)) => { + let header = &relay.data.message.header; + + info!( + self.log(), + "Received builder payload with local error"; + "relay_block_hash" => ?header.block_hash(), + "local_error" => ?local_error, + "parent_hash" => ?parent_hash, + ); + + match verify_builder_bid( + &relay, + parent_hash, + payload_attributes, + None, + self.inner.builder_profit_threshold, + current_fork, + spec, + ) { + Ok(()) => Ok(ProvenancedPayload::Builder( + BlockProposalContentsType::Blinded( + BlockProposalContents::Payload { + payload: relay.data.message.header, + block_value: relay.data.message.value, + _phantom: PhantomData, + }), + )), + // If the payload is valid then use it. The local EE failed + // to produce a payload so we have no alternative. + Err(e) if !e.payload_invalid() => Ok(ProvenancedPayload::Builder( + BlockProposalContentsType::Blinded( + BlockProposalContents::Payload { + payload: relay.data.message.header, + block_value: relay.data.message.value, + _phantom: PhantomData, + }), + )), + Err(reason) => { + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS, + &[reason.as_ref().as_ref()], + ); + crit!( + self.log(), + "Builder returned invalid payload"; + "info" => "no local payload either - unable to propose block", + "reason" => %reason, + "relay_block_hash" => ?header.block_hash(), + "parent_hash" => ?parent_hash, + ); + Err(Error::CannotProduceHeader) + } + } + } + (Err(relay_error), Err(local_error)) => { + crit!( + self.log(), + "Unable to produce execution payload"; + "info" => "the local EL and builder both failed - unable to propose block", + "relay_error" => ?relay_error, + "local_error" => ?local_error, + "parent_hash" => ?parent_hash, + ); + + Err(Error::CannotProduceHeader) + } + (Ok(None), Err(local_error)) => { + crit!( + self.log(), + "Unable to produce execution payload"; + "info" => "the local EL failed and the builder returned nothing - \ + the block proposal will be missed", + "local_error" => ?local_error, + "parent_hash" => ?parent_hash, + ); + + Err(Error::CannotProduceHeader) + } + }; + } + ChainHealth::Unhealthy(condition) => info!( + self.log(), + "Chain is unhealthy, using local payload"; + "info" => "this helps protect the network. the --builder-fallback flags \ + can adjust the expected health conditions.", + "failed_condition" => ?condition + ), + // Intentional no-op, so we never attempt builder API proposals pre-merge. + ChainHealth::PreMerge => (), + ChainHealth::Optimistic => info!( + self.log(), + "Chain is optimistic; can't build payload"; + "info" => "the local execution engine is syncing and the builder network \ + cannot safely be used - unable to propose block" + ), + } + } + println!("YOOOO"); + let payload = self.get_full_payload_caching::>( + parent_hash, + payload_attributes, + forkchoice_update_params, + current_fork, + ) + .await?; + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Blinded(payload))) + } + async fn get_blinded_payload>( &self, parent_hash: ExecutionBlockHash, diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index a6ff560ae60..4f0e30d92eb 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -26,7 +26,7 @@ mod validator; mod validator_inclusion; mod version; -use crate::validator::{produce_block_v2, produce_block_v3}; +use crate::validator::{produce_block_v2, produce_blinded_block_v2, produce_block_v3}; use beacon_chain::{ attestation_verification::VerifiedAttestation, observed_operations::ObservationOutcome, validator_monitor::timestamp_now, AttestationError as AttnError, BeaconChain, BeaconChainError, @@ -3060,44 +3060,7 @@ 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, _) = 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)?; - - // Pose as a V2 endpoint so we return the fork `version`. - fork_versioned_response(V2, fork_name, block) - .map(|response| warp::reply::json(&response).into_response()) - .map(|res| add_consensus_version_header(res, fork_name)) + produce_blinded_block_v2(EndpointVersion(2), chain, slot, query).await }) }, ); diff --git a/beacon_node/http_api/src/validator.rs b/beacon_node/http_api/src/validator.rs index ccd8f03e44c..b45266a398d 100644 --- a/beacon_node/http_api/src/validator.rs +++ b/beacon_node/http_api/src/validator.rs @@ -76,16 +76,21 @@ pub async fn produce_blinded_block_v2( slot, query.graffiti.map(Into::into), randao_verification, - BlockProductionVersion::FullV2, + BlockProductionVersion::BlindedV2, ) .await .map_err(warp_utils::reject::block_production_error)?; match block_response { - BeaconBlockAndStateResponse::Full((_, _)) => { - Err(warp_utils::reject::custom_server_error( - "Returned a blinded block. It should be impossible to return a blinded block via the Full Payload V2 block fetching flow.".to_string() - )) + BeaconBlockAndStateResponse::Full((block, _)) => { + let fork_name = block + .to_ref() + .fork_name(&chain.spec) + .map_err(inconsistent_fork_rejection)?; + + fork_versioned_response(endpoint_version, fork_name, block) + .map(|response| warp::reply::json(&response).into_response()) + .map(|res| add_consensus_version_header(res, fork_name)) } BeaconBlockAndStateResponse::Blinded((block, _)) => { let fork_name = block