diff --git a/beacon_node/beacon_chain/src/blob_verification.rs b/beacon_node/beacon_chain/src/blob_verification.rs index 1d7f9cc3c52..48e811076a6 100644 --- a/beacon_node/beacon_chain/src/blob_verification.rs +++ b/beacon_node/beacon_chain/src/blob_verification.rs @@ -7,9 +7,9 @@ use crate::{kzg_utils, BeaconChainError}; use state_processing::per_block_processing::eip4844::eip4844::verify_kzg_commitments_against_transactions; use types::signed_beacon_block::BlobReconstructionError; use types::{ - BeaconBlockRef, BeaconStateError, BlobsSidecar, EthSpec, Hash256, KzgCommitment, - SignedBeaconBlock, SignedBeaconBlockAndBlobsSidecar, SignedBeaconBlockHeader, Slot, - Transactions, + BeaconBlock, BeaconBlockRef, BeaconStateError, BlobsSidecar, EthSpec, ExecutionPayload, + ExecutionPayloadHeader, Hash256, KzgCommitment, SignedBeaconBlock, + SignedBeaconBlockAndBlobsSidecar, SignedBeaconBlockHeader, Slot, Transactions, }; use types::{Epoch, ExecPayload}; diff --git a/beacon_node/builder_client/src/lib.rs b/beacon_node/builder_client/src/lib.rs index fecf6512ac8..265a985969b 100644 --- a/beacon_node/builder_client/src/lib.rs +++ b/beacon_node/builder_client/src/lib.rs @@ -1,8 +1,7 @@ use eth2::types::builder_bid::SignedBuilderBid; use eth2::types::{ - AbstractExecPayload, BlindedPayload, EthSpec, ExecutionBlockHash, ExecutionPayload, - ForkVersionedResponse, PublicKeyBytes, SignedBeaconBlock, SignedValidatorRegistrationData, - Slot, + AbstractExecPayload, BlindedPayload, EthSpec, ExecutionBlockHash, ForkVersionedResponse, + PublicKeyBytes, SignedBeaconBlock, SignedValidatorRegistrationData, Slot, }; pub use eth2::Error; use eth2::{ok_or_error, StatusCode}; @@ -135,10 +134,10 @@ impl BuilderHttpClient { } /// `POST /eth/v1/builder/blinded_blocks` - pub async fn post_builder_blinded_blocks( + pub async fn post_builder_blinded_blocks( &self, blinded_block: &SignedBeaconBlock>, - ) -> Result>, Error> { + ) -> Result, Error> { let mut path = self.server.full.clone(); path.path_segments_mut() diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index f07fa793290..c7a65e6dfb5 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -42,6 +42,7 @@ use tokio::{ use tokio_stream::wrappers::WatchStream; use tree_hash::TreeHash; use types::consts::eip4844::BLOB_TX_TYPE; +use types::execution_payload::{ExecutionPayloadAndBlobsSidecar, PayloadWrapper}; use types::transaction::{AccessTuple, BlobTransaction, EcdsaSignature, SignedBlobTransaction}; use types::Withdrawals; use types::{ @@ -49,7 +50,9 @@ use types::{ BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadEip4844, ExecutionPayloadMerge, ForkName, }; -use types::{AbstractExecPayload, BeaconStateError, ExecPayload, VersionedHash}; +use types::{ + AbstractExecPayload, BeaconStateError, Blob, ExecPayload, KzgCommitment, VersionedHash, +}; use types::{ ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock, Slot, Transaction, Uint256, @@ -797,7 +800,7 @@ impl ExecutionLayer { self.log(), "Requested blinded execution payload"; "relay_fee_recipient" => match &relay_result { - Ok(Some(r)) => format!("{:?}", r.data.message.header.fee_recipient()), + Ok(Some(r)) => format!("{:?}", r.data.message.header().fee_recipient()), Ok(None) => "empty response".to_string(), Err(_) => "request failed".to_string(), }, @@ -833,7 +836,7 @@ impl ExecutionLayer { Ok(ProvenancedPayload::Local(local)) } (Ok(Some(relay)), Ok(local)) => { - let header = &relay.data.message.header; + let header = relay.data.message.header().clone(); info!( self.log(), @@ -864,12 +867,30 @@ impl ExecutionLayer { current_fork, spec, ) { - Ok(()) => Ok(ProvenancedPayload::Builder( - BlockProposalContents::Payload { - payload: relay.data.message.header, - block_value: relay.data.message.value, - }, - )), + Ok(()) => { + Ok(ProvenancedPayload::Builder(match relay.version.unwrap() { + ForkName::Base + | ForkName::Altair + | ForkName::Merge + | ForkName::Capella => BlockProposalContents::Payload { + payload: relay.data.message.header, + block_value: relay.data.message.value, + }, + ForkName::Eip4844 => { + BlockProposalContents::PayloadAndBlobs { + payload: relay.data.message.header, + block_value: relay.data.message.value, + blobs: VariableList::default(), + kzg_commitments: relay + .data + .message + .blob_kzg_commitments() + .unwrap() + .clone(), + } + } + })) + } Err(reason) if !reason.payload_invalid() => { info!( self.log(), @@ -899,7 +920,7 @@ impl ExecutionLayer { } } (Ok(Some(relay)), Err(local_error)) => { - let header = &relay.data.message.header; + let header = relay.data.message.header().clone(); info!( self.log(), @@ -918,20 +939,56 @@ impl ExecutionLayer { current_fork, spec, ) { - Ok(()) => Ok(ProvenancedPayload::Builder( - BlockProposalContents::Payload { - payload: relay.data.message.header, - block_value: relay.data.message.value, - }, - )), + Ok(()) => { + Ok(ProvenancedPayload::Builder(match relay.version.unwrap() { + ForkName::Base + | ForkName::Altair + | ForkName::Merge + | ForkName::Capella => BlockProposalContents::Payload { + payload: relay.data.message.header, + block_value: relay.data.message.value, + }, + ForkName::Eip4844 => { + BlockProposalContents::PayloadAndBlobs { + payload: relay.data.message.header, + block_value: relay.data.message.value, + blobs: VariableList::default(), + kzg_commitments: relay + .data + .message + .blob_kzg_commitments() + .unwrap() + .clone(), + } + } + })) + } // 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( - BlockProposalContents::Payload { - payload: relay.data.message.header, - block_value: relay.data.message.value, - }, - )), + Err(e) if !e.payload_invalid() => { + Ok(ProvenancedPayload::Builder(match relay.version.unwrap() { + ForkName::Base + | ForkName::Altair + | ForkName::Merge + | ForkName::Capella => BlockProposalContents::Payload { + payload: relay.data.message.header, + block_value: relay.data.message.value, + }, + ForkName::Eip4844 => { + BlockProposalContents::PayloadAndBlobs { + payload: relay.data.message.header, + block_value: relay.data.message.value, + blobs: VariableList::default(), + kzg_commitments: relay + .data + .message + .blob_kzg_commitments() + .unwrap() + .clone(), + } + } + })) + } Err(reason) => { metrics::inc_counter_vec( &metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS, @@ -1779,7 +1836,8 @@ impl ExecutionLayer { &self, block_root: Hash256, block: &SignedBeaconBlock>, - ) -> Result, Error> { + fork_name: ForkName, + ) -> Result, Error> { debug!( self.log(), "Sending block to builder"; @@ -1789,29 +1847,31 @@ impl ExecutionLayer { if let Some(builder) = self.builder() { let (payload_result, duration) = timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async { - builder - .post_builder_blinded_blocks(block) - .await - .map_err(Error::Builder) - .map(|d| d.data) + match fork_name { + ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => { + let payload = builder + .post_builder_blinded_blocks::>(block) + .await + .map_err(Error::Builder) + .map(|d| d.data)?; + Ok(PayloadWrapper::Payload(payload)) + } + ForkName::Eip4844 => { + let payload_and_blob = builder + .post_builder_blinded_blocks::>(block) + .await + .map_err(Error::Builder) + .map(|d| d.data)?; + Ok(PayloadWrapper::PayloadAndBlob(payload_and_blob)) + }, + } }) .await; - match &payload_result { - Ok(payload) => { - metrics::inc_counter_vec( - &metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME, - &[metrics::SUCCESS], - ); - info!( - self.log(), - "Builder successfully revealed payload"; - "relay_response_ms" => duration.as_millis(), - "block_root" => ?block_root, - "fee_recipient" => ?payload.fee_recipient(), - "block_hash" => ?payload.block_hash(), - "parent_hash" => ?payload.parent_hash() - ) + let payload_maybe = match &payload_result { + Ok(PayloadWrapper::Payload(payload)) => Some(payload), + Ok(PayloadWrapper::PayloadAndBlob(payload_and_blob)) => { + Some(&payload_and_blob.execution_payload) } Err(e) => { metrics::inc_counter_vec( @@ -1830,8 +1890,29 @@ impl ExecutionLayer { .execution_payload() .map(|payload| format!("{}", payload.parent_hash())) .unwrap_or_else(|_| "unknown".to_string()) - ) + ); + None + } + }; + + match payload_maybe { + Some(payload) => { + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME, + &[metrics::SUCCESS], + ); + + info!( + self.log(), + "Builder successfully revealed payload"; + "relay_response_ms" => duration.as_millis(), + "block_root" => ?block_root, + "fee_recipient" => ?payload.fee_recipient(), + "block_hash" => ?payload.block_hash(), + "parent_hash" => ?payload.parent_hash() + ); } + None => {} } payload_result @@ -1954,11 +2035,11 @@ fn verify_builder_bid>( spec: &ChainSpec, ) -> Result<(), Box> { let is_signature_valid = bid.data.verify_signature(spec); - let header = &bid.data.message.header; - let payload_value = bid.data.message.value; + let header = &bid.data.message.header(); + let payload_value = bid.data.message.value().clone(); // Avoid logging values that we can't represent with our Prometheus library. - let payload_value_gwei = bid.data.message.value / 1_000_000_000; + let payload_value_gwei = bid.data.message.value() / 1_000_000_000; if payload_value_gwei <= Uint256::from(i64::max_value()) { metrics::set_gauge_vec( &metrics::EXECUTION_LAYER_PAYLOAD_BIDS, @@ -2007,7 +2088,7 @@ fn verify_builder_bid>( } else if !is_signature_valid { Err(Box::new(InvalidBuilderPayload::Signature { signature: bid.data.signature.clone(), - pubkey: bid.data.message.pubkey, + pubkey: *bid.data.message.pubkey(), })) } else if payload_withdrawals_root != expected_withdrawals_root { Err(Box::new(InvalidBuilderPayload::WithdrawalsRoot { diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index ec3d01085a8..3595bbf083b 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -53,6 +53,7 @@ use sysinfo::{System, SystemExt}; use system_health::observe_system_health_bn; use tokio::sync::mpsc::{Sender, UnboundedSender}; use tokio_stream::{wrappers::BroadcastStream, StreamExt}; +use types::signed_block_and_blobs::BlockWrapper; use types::{ Attestation, AttestationData, AttesterSlashing, BeaconStateError, BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload, @@ -1123,7 +1124,8 @@ pub fn serve( chain: Arc>, network_tx: UnboundedSender>, log: Logger| async move { - publish_blocks::publish_block(None, block, chain, &network_tx, log) + let block_wrapper = BlockWrapper::new(block); + publish_blocks::publish_block(None, block_wrapper, chain, &network_tx, log) .await .map(|()| warp::reply()) }, diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index e41a90c9550..8d858043503 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -10,22 +10,24 @@ use slot_clock::SlotClock; use std::sync::Arc; use tokio::sync::mpsc::UnboundedSender; use tree_hash::TreeHash; +use types::execution_payload::PayloadWrapper; use types::{ - AbstractExecPayload, BlindedPayload, EthSpec, ExecPayload, ExecutionBlockHash, FullPayload, - Hash256, SignedBeaconBlock, SignedBeaconBlockAndBlobsSidecar, + AbstractExecPayload, BeaconBlock, BlindedPayload, BlobsSidecar, EthSpec, ExecPayload, + ExecutionBlockHash, ExecutionPayload, FullPayload, Hash256, SignedBeaconBlock, + SignedBeaconBlockAndBlobsSidecar, }; use warp::Rejection; /// Handles a request from the HTTP API for full blocks. pub async fn publish_block( block_root: Option, - block: Arc>, + block_wrapper: BlockWrapper, chain: Arc>, network_tx: &UnboundedSender>, log: Logger, ) -> Result<(), Rejection> { let seen_timestamp = timestamp_now(); - + let block = block_wrapper.block_cloned(); //FIXME(sean) have to move this to prior to publishing because it's included in the blobs sidecar message. //this may skew metrics let block_root = block_root.unwrap_or_else(|| block.canonical_root()); @@ -34,21 +36,35 @@ pub async fn publish_block( // specification is very clear that this is the desired behaviour. let wrapped_block: BlockWrapper = if matches!(block.as_ref(), &SignedBeaconBlock::Eip4844(_)) { - if let Some(sidecar) = chain.blob_cache.pop(&block_root) { - let block_and_blobs = SignedBeaconBlockAndBlobsSidecar { - beacon_block: block, - blobs_sidecar: Arc::new(sidecar), - }; - crate::publish_pubsub_message( - network_tx, - PubsubMessage::BeaconBlockAndBlobsSidecars(block_and_blobs.clone()), - )?; - block_and_blobs.into() - } else { - //FIXME(sean): This should probably return a specific no-blob-cached error code, beacon API coordination required - return Err(warp_utils::reject::broadcast_without_import( - "no blob cached for block".into(), - )); + let maybe_sidecar = block_wrapper + .blobs(Some(block_root)) + .ok() + .flatten() + .or_else(|| { + chain + .blob_cache + .pop(&block_root) + .map(|blob_sidecar| Arc::new(blob_sidecar)) + }); + + match maybe_sidecar { + Some(sidecar) => { + let block_and_blobs = SignedBeaconBlockAndBlobsSidecar { + beacon_block: block, + blobs_sidecar: sidecar, + }; + crate::publish_pubsub_message( + network_tx, + PubsubMessage::BeaconBlockAndBlobsSidecars(block_and_blobs.clone()), + )?; + block_and_blobs.into() + } + None => { + //FIXME(sean): This should probably return a specific no-blob-cached error code, beacon API coordination required + return Err(warp_utils::reject::broadcast_without_import( + "no blob cached for block".into(), + )); + } } } else { crate::publish_pubsub_message(network_tx, PubsubMessage::BeaconBlock(block.clone()))?; @@ -175,15 +191,8 @@ pub async fn publish_blinded_block( log: Logger, ) -> Result<(), Rejection> { let block_root = block.canonical_root(); - let full_block = reconstruct_block(chain.clone(), block_root, block, log.clone()).await?; - publish_block::( - Some(block_root), - Arc::new(full_block), - chain, - network_tx, - log, - ) - .await + let block_wrapper = reconstruct_block(chain.clone(), block_root, block, log.clone()).await?; + publish_block::(Some(block_root), block_wrapper, chain, network_tx, log).await } /// Deconstruct the given blinded block, and construct a full block. This attempts to use the @@ -194,52 +203,80 @@ async fn reconstruct_block( block_root: Hash256, block: SignedBeaconBlock>, log: Logger, -) -> Result>, Rejection> { - let full_payload = if let Ok(payload_header) = block.message().body().execution_payload() { - let el = chain.execution_layer.as_ref().ok_or_else(|| { - warp_utils::reject::custom_server_error("Missing execution layer".to_string()) - })?; - - // If the execution block hash is zero, use an empty payload. - let full_payload = if payload_header.block_hash() == ExecutionBlockHash::zero() { - FullPayload::default_at_fork( - chain - .spec - .fork_name_at_epoch(block.slot().epoch(T::EthSpec::slots_per_epoch())), - ) +) -> Result, Rejection> { + let payload_header = block.message().body().execution_payload().map_err(|_| { + warp_utils::reject::custom_server_error("Unable to add payload to block".to_string()) + })?; + + let el = chain.execution_layer.as_ref().ok_or_else(|| { + warp_utils::reject::custom_server_error("Missing execution layer".to_string()) + })?; + + // If the execution block hash is zero, use an empty payload. + let block_wrapper = if payload_header.block_hash() == ExecutionBlockHash::zero() { + let payload = FullPayload::default_at_fork( + chain + .spec + .fork_name_at_epoch(block.slot().epoch(T::EthSpec::slots_per_epoch())), + ) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "Default payload construction error: {e:?}" + )) + })? + .into(); + try_into_full_block_wrapper(block, Some(payload), None) + // If we already have an execution payload with this transactions root cached, use it. + } else if let Some(cached_payload) = el.get_payload_by_root(&payload_header.tree_hash_root()) { + info!(log, "Reconstructing a full block using a local payload"; "block_hash" => ?cached_payload.block_hash()); + block.try_into_full_block_wrapper(Some(cached_payload), None) + // Otherwise, this means we are attempting a blind block proposal. + } else { + let fork_name = chain + .spec + .fork_name_at_epoch(block.slot().epoch(T::EthSpec::slots_per_epoch())); + let payload_wrapper = el + .propose_blinded_beacon_block(block_root, &block, fork_name) + .await .map_err(|e| { warp_utils::reject::custom_server_error(format!( - "Default payload construction error: {e:?}" + "Blind block proposal failed: {:?}", + e )) - })? - .into() - // If we already have an execution payload with this transactions root cached, use it. - } else if let Some(cached_payload) = - el.get_payload_by_root(&payload_header.tree_hash_root()) - { - info!(log, "Reconstructing a full block using a local payload"; "block_hash" => ?cached_payload.block_hash()); - cached_payload - // Otherwise, this means we are attempting a blind block proposal. - } else { - let full_payload = el - .propose_blinded_beacon_block(block_root, &block) - .await - .map_err(|e| { - warp_utils::reject::custom_server_error(format!( - "Blind block proposal failed: {:?}", - e - )) - })?; - info!(log, "Successfully published a block to the builder network"; "block_hash" => ?full_payload.block_hash()); - full_payload + })?; + + let (full_payload, maybe_blobs) = match payload_wrapper { + PayloadWrapper::Payload(payload) => (payload, None), + PayloadWrapper::PayloadAndBlob(payload_and_blob) => ( + payload_and_blob.execution_payload, + Some(payload_and_blob.blobs_sidecar), + ), }; - Some(full_payload) - } else { - None + info!(log, "Successfully published a block to the builder network"; "block_hash" => ?full_payload.block_hash()); + try_into_full_block_wrapper(block, Some(full_payload), maybe_blobs) }; - block.try_into_full_block(full_payload).ok_or_else(|| { + block_wrapper.ok_or_else(|| { warp_utils::reject::custom_server_error("Unable to add payload to block".to_string()) }) } + +fn try_into_full_block_wrapper( + beacon_block: SignedBeaconBlock>, + execution_payload: Option>, + blobs_sidecar: Option>, +) -> Option> { + let maybe_full_block = beacon_block.try_into_full_block(execution_payload); + + maybe_full_block.map(|full_block| match full_block { + SignedBeaconBlock::Base(_) + | SignedBeaconBlock::Altair(_) + | SignedBeaconBlock::Merge(_) + | SignedBeaconBlock::Capella(_) => BlockWra(Arc::new(full_block)), + SignedBeaconBlock::Eip4844(_) => BlockWrapper::new_with_blobs( + Arc::new(full_block), + Arc::new(blobs_sidecar.unwrap_or_default()), + ), + }) +} diff --git a/consensus/types/src/builder_bid.rs b/consensus/types/src/builder_bid.rs index e922e81c706..ff86a6bc293 100644 --- a/consensus/types/src/builder_bid.rs +++ b/consensus/types/src/builder_bid.rs @@ -1,3 +1,4 @@ +use super::KzgCommitment; use crate::{ AbstractExecPayload, ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, ForkName, ForkVersionDeserialize, SignedRoot, Uint256, @@ -6,19 +7,37 @@ use bls::PublicKeyBytes; use bls::Signature; use serde::{Deserialize as De, Deserializer, Serialize as Ser, Serializer}; use serde_derive::{Deserialize, Serialize}; -use serde_with::{serde_as, DeserializeAs, SerializeAs}; +use serde_with::{As, DeserializeAs, SerializeAs}; +use ssz_types::VariableList; use std::marker::PhantomData; +use superstruct::superstruct; use tree_hash_derive::TreeHash; -#[serde_as] +#[superstruct( + variants(Merge, Eip4844), + variant_attributes( + derive(PartialEq, Debug, Serialize, Deserialize, TreeHash, Clone), + serde(bound = "E: EthSpec, Payload: ExecPayload", deny_unknown_fields) + ) +)] #[derive(PartialEq, Debug, Serialize, Deserialize, TreeHash, Clone)] -#[serde(bound = "E: EthSpec, Payload: ExecPayload")] +#[serde( + bound = "E: EthSpec, Payload: ExecPayload", + deny_unknown_fields, + untagged +)] +#[tree_hash(enum_behaviour = "transparent")] pub struct BuilderBid> { - #[serde_as(as = "BlindedPayloadAsHeader")] + #[serde(with = "As::>")] pub header: Payload, + #[serde(with = "eth2_serde_utils::quoted_u256")] pub value: Uint256, pub pubkey: PublicKeyBytes, + + #[superstruct(only(Eip4844))] + pub blob_kzg_commitments: VariableList, + #[serde(skip)] #[tree_hash(skip_hashing)] _phantom_data: PhantomData, @@ -58,12 +77,25 @@ impl> ForkVersionDeserialize let payload_header = ExecutionPayloadHeader::deserialize_by_fork::<'de, D>(helper.header, fork_name)?; - Ok(Self { - header: Payload::try_from(payload_header).map_err(convert_err)?, - value: helper.value, - pubkey: helper.pubkey, - _phantom_data: Default::default(), - }) + match fork_name { + ForkName::Base | ForkName::Altair => Err(serde::de::Error::custom( + "BuilderBid failed to deserialize: unable to convert payload header to payload", + )), + ForkName::Merge | ForkName::Capella => Ok(BuilderBid::Merge(BuilderBidMerge { + header: Payload::try_from(payload_header).map_err(convert_err)?, + value: helper.value, + pubkey: helper.pubkey, + _phantom_data: PhantomData, + })), + ForkName::Eip4844 => Ok(BuilderBid::Eip4844(BuilderBidEip4844 { + header: Payload::try_from(payload_header).map_err(convert_err)?, + value: helper.value, + pubkey: helper.pubkey, + // TODO + blob_kzg_commitments: VariableList::empty(), + _phantom_data: PhantomData, + })), + } } } @@ -115,7 +147,7 @@ impl<'de, E: EthSpec, Payload: AbstractExecPayload> DeserializeAs<'de, Payloa impl> SignedBuilderBid { pub fn verify_signature(&self, spec: &ChainSpec) -> bool { self.message - .pubkey + .pubkey() .decompress() .map(|pubkey| { let domain = spec.get_builder_domain(); @@ -125,3 +157,119 @@ impl> SignedBuilderBid { .unwrap_or(false) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{BlindedPayload, MainnetEthSpec}; + + type Spec = MainnetEthSpec; + type Payload = BlindedPayload; + + fn deserialize_bid(str: &str) -> BuilderBid { + serde_json::from_str(str).expect("should deserialize to BuilderBid") + } + + fn serialize_bid(bid: BuilderBid) -> String { + serde_json::to_string(&bid).expect("should serialize to json string") + } + + fn to_minified_json(json: &str) -> String { + let mut json_mut = String::from(json); + json_mut.retain(|c| !c.is_whitespace()); + json_mut + } + + #[test] + fn test_serde_builder_bid_merge() { + let expected_json = to_minified_json( + r#"{ + "header": { + "parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09", + "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "block_number": "1", + "gas_limit": "1", + "gas_used": "1", + "timestamp": "1", + "extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "base_fee_per_gas": "1", + "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "transactions_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + }, + "value": "1", + "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" + }"#, + ); + let bid = deserialize_bid(&expected_json); + let actual_json = serialize_bid(bid); + assert_eq!(actual_json, expected_json); + } + + #[test] + fn test_serde_builder_bid_capella() { + let expected_json = to_minified_json( + r#"{ + "header": { + "parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09", + "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "block_number": "1", + "gas_limit": "1", + "gas_used": "1", + "timestamp": "1", + "extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "base_fee_per_gas": "1", + "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "transactions_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "withdrawals_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + }, + "value": "1", + "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" + }"#, + ); + let bid = deserialize_bid(&expected_json); + let actual_json = serialize_bid(bid); + assert_eq!(actual_json, expected_json); + } + + #[test] + fn test_serde_builder_bid_eip4844() { + let expected_json = to_minified_json( + r#"{ + "header": { + "parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09", + "state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "block_number": "1", + "gas_limit": "1", + "gas_used": "1", + "timestamp": "1", + "extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "base_fee_per_gas": "1", + "excess_data_gas": "1", + "block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "transactions_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "withdrawals_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + }, + "value": "1", + "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", + "blob_kzg_commitments": [ + "0xa94170080872584e54a1cf092d845703b13907f2e6b3b1c0ad573b910530499e3bcd48c6378846b80d2bfa58c81cf3d5" + ] + }"#, + ); + let bid = deserialize_bid(&expected_json); + let actual_json = serialize_bid(bid); + assert_eq!(actual_json, expected_json); + } +} diff --git a/consensus/types/src/execution_payload.rs b/consensus/types/src/execution_payload.rs index 6e055d0a79a..c2d346c2a0e 100644 --- a/consensus/types/src/execution_payload.rs +++ b/consensus/types/src/execution_payload.rs @@ -171,3 +171,19 @@ impl ForkVersionDeserialize for ExecutionPayload { }) } } + +#[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +pub struct ExecutionPayloadAndBlobsSidecar { + pub execution_payload: ExecutionPayload, + pub blobs_sidecar: BlobsSidecar, +} + +/// A wrapper over a [`ExecutionPayload`] or a [`ExecutionPayloadAndBlobsSidecar`]. +#[derive(Clone, Debug, Derivative)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +pub enum PayloadWrapper { + Payload(ExecutionPayload), + PayloadAndBlob(ExecutionPayloadAndBlobsSidecar), +}