Builder flow for Deneb & Blobs (#4428)

* Add Deneb builder flow types with generics

* Update validator client `get_blinded_blocks` call to support Deneb

* `produceBlindedBlock` endpoint updates:
- Handle new Deneb BuilderBid response from builder endpoint (new BlindedBlobsBundle type)
- Build BlockContents response (containing kzg_commitments, proof and blinded_blob_sidecars)

* Appease Clippy lint

* Partial implementation of submit blinded block & blobs. Refactor existing `BlobSidecar` related types to support blinded blobs.

* Add associated types for BlockProposal

* Rename `AbstractSidecar` to `Sidecar`

* Remove blob cache as it's no longer necessary

* Remove unnecessary enum variant

* Clean up

* Hanlde unblinded blobs and publish full block contents

* Fix tests

* Add local EL blobs caching in blinded flow

* Remove BlockProposal and move associated Sidecar trait to AbstractExecPayload to simplify changes

* add blob roots associated type

* move raw blobs associated type to sidecar trait

* Fix todos and improve error handling

* Consolidate BlobsBundle from `execution_layer` into `consensus/types`

* Rename RawBlobs, Blobs, and BlobRoots

* Use `BlobRoots` type alias

* Update error message.

Co-authored-by: realbigsean <seananderson33@GMAIL.com>

* update builder bid type

# Conflicts:
#	consensus/types/src/builder_bid.rs

* Fix lint

* remove generic from builder bid

---------

Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
Jimmy Chen
2023-08-10 23:32:49 +10:00
committed by GitHub
parent fddd4e4c87
commit 0b7a426946
32 changed files with 1027 additions and 499 deletions

View File

@@ -1,22 +1,25 @@
use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProductionError};
use eth2::types::{BeaconBlockAndBlobSidecars, BlockContents};
use std::sync::Arc;
use types::{AbstractExecPayload, BeaconBlock, ForkName};
use beacon_chain::BlockProductionError;
use eth2::types::{BeaconBlockAndBlobSidecars, BlindedBeaconBlockAndBlobSidecars, BlockContents};
use types::{
BeaconBlock, BlindedBlobSidecarList, BlindedPayload, BlobSidecarList, EthSpec, ForkName,
FullPayload,
};
type Error = warp::reject::Rejection;
type FullBlockContents<E> = BlockContents<E, FullPayload<E>>;
type BlindedBlockContents<E> = BlockContents<E, BlindedPayload<E>>;
pub fn build_block_contents<T: BeaconChainTypes, Payload: AbstractExecPayload<T::EthSpec>>(
pub fn build_block_contents<E: EthSpec>(
fork_name: ForkName,
chain: Arc<BeaconChain<T>>,
block: BeaconBlock<T::EthSpec, Payload>,
) -> Result<BlockContents<T::EthSpec, Payload>, Error> {
block: BeaconBlock<E, FullPayload<E>>,
maybe_blobs: Option<BlobSidecarList<E>>,
) -> Result<FullBlockContents<E>, Error> {
match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
Ok(BlockContents::Block(block))
}
ForkName::Deneb => {
let block_root = &block.canonical_root();
if let Some(blob_sidecars) = chain.proposal_blob_cache.pop(block_root) {
if let Some(blob_sidecars) = maybe_blobs {
let block_and_blobs = BeaconBlockAndBlobSidecars {
block,
blob_sidecars,
@@ -25,7 +28,33 @@ pub fn build_block_contents<T: BeaconChainTypes, Payload: AbstractExecPayload<T:
Ok(BlockContents::BlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
BlockProductionError::NoBlobsCached,
BlockProductionError::MissingBlobs,
))
}
}
}
}
pub fn build_blinded_block_contents<E: EthSpec>(
fork_name: ForkName,
block: BeaconBlock<E, BlindedPayload<E>>,
maybe_blobs: Option<BlindedBlobSidecarList<E>>,
) -> Result<BlindedBlockContents<E>, Error> {
match fork_name {
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
Ok(BlockContents::Block(block))
}
ForkName::Deneb => {
if let Some(blinded_blob_sidecars) = maybe_blobs {
let block_and_blobs = BlindedBeaconBlockAndBlobSidecars {
blinded_block: block,
blinded_blob_sidecars,
};
Ok(BlockContents::BlindedBlockAndBlobSidecars(block_and_blobs))
} else {
Err(warp_utils::reject::block_production_error(
BlockProductionError::MissingBlobs,
))
}
}

View File

@@ -1440,14 +1440,14 @@ pub fn serve<T: BeaconChainTypes>(
.and(network_tx_filter.clone())
.and(log_filter.clone())
.and_then(
|block: SignedBlockContents<T::EthSpec, BlindedPayload<_>>,
|block_contents: SignedBlockContents<T::EthSpec, BlindedPayload<_>>,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger| {
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
publish_blocks::publish_blinded_block(
block,
block_contents,
chain,
&network_tx,
log,
@@ -3065,7 +3065,7 @@ pub fn serve<T: BeaconChainTypes>(
ProduceBlockVerification::VerifyRandao
};
let (block, _) = chain
let (block, _, maybe_blobs) = chain
.produce_block_with_verification::<FullPayload<T::EthSpec>>(
randao_reveal,
slot,
@@ -3080,9 +3080,9 @@ pub fn serve<T: BeaconChainTypes>(
.map_err(inconsistent_fork_rejection)?;
let block_contents =
build_block_contents::build_block_contents(fork_name, chain, block);
build_block_contents::build_block_contents(fork_name, block, maybe_blobs)?;
fork_versioned_response(endpoint_version, fork_name, block_contents?)
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))
})
@@ -3129,7 +3129,7 @@ pub fn serve<T: BeaconChainTypes>(
ProduceBlockVerification::VerifyRandao
};
let (block, _) = chain
let (block, _, maybe_blobs) = chain
.produce_block_with_verification::<BlindedPayload<T::EthSpec>>(
randao_reveal,
slot,
@@ -3143,8 +3143,14 @@ pub fn serve<T: BeaconChainTypes>(
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let block_contents = build_block_contents::build_blinded_block_contents(
fork_name,
block,
maybe_blobs,
)?;
// Pose as a V2 endpoint so we return the fork `version`.
fork_versioned_response(V2, fork_name, block)
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))
})

View File

@@ -7,7 +7,7 @@ use beacon_chain::{
IntoGossipVerifiedBlockContents, NotifyExecutionLayer,
};
use eth2::types::BroadcastValidation;
use eth2::types::SignedBlockContents;
use eth2::types::{FullPayloadContents, SignedBlockContents};
use execution_layer::ProvenancedPayload;
use lighthouse_network::PubsubMessage;
use network::NetworkMessage;
@@ -267,15 +267,15 @@ pub async fn publish_block<T: BeaconChainTypes, B: IntoGossipVerifiedBlockConten
/// Handles a request from the HTTP API for blinded blocks. This converts blinded blocks into full
/// blocks before publishing.
pub async fn publish_blinded_block<T: BeaconChainTypes>(
block: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
block_contents: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
chain: Arc<BeaconChain<T>>,
network_tx: &UnboundedSender<NetworkMessage<T::EthSpec>>,
log: Logger,
validation_level: BroadcastValidation,
) -> Result<(), Rejection> {
let block_root = block.signed_block().canonical_root();
let block_root = block_contents.signed_block().canonical_root();
let full_block: ProvenancedBlock<T, SignedBlockContents<T::EthSpec>> =
reconstruct_block(chain.clone(), block_root, block, log.clone()).await?;
reconstruct_block(chain.clone(), block_root, block_contents, log.clone()).await?;
publish_block::<T, _>(
Some(block_root),
full_block,
@@ -293,25 +293,21 @@ pub async fn publish_blinded_block<T: BeaconChainTypes>(
pub async fn reconstruct_block<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
block_root: Hash256,
block: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
block_contents: SignedBlockContents<T::EthSpec, BlindedPayload<T::EthSpec>>,
log: Logger,
) -> Result<ProvenancedBlock<T, SignedBlockContents<T::EthSpec>>, Rejection> {
let full_payload_opt = if let Ok(payload_header) =
block.signed_block().message().body().execution_payload()
{
let block = block_contents.signed_block();
let full_payload_opt = 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() {
let full_payload_contents = if payload_header.block_hash() == ExecutionBlockHash::zero() {
let payload = FullPayload::default_at_fork(
chain.spec.fork_name_at_epoch(
block
.signed_block()
.slot()
.epoch(T::EthSpec::slots_per_epoch()),
),
chain
.spec
.fork_name_at_epoch(block.slot().epoch(T::EthSpec::slots_per_epoch())),
)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
@@ -319,7 +315,7 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
))
})?
.into();
ProvenancedPayload::Local(payload)
ProvenancedPayload::Local(FullPayloadContents::Payload(payload))
// 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())
@@ -336,14 +332,14 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
late_block_logging(
&chain,
timestamp_now(),
block.signed_block().message(),
block.message(),
block_root,
"builder",
&log,
);
let full_payload = el
.propose_blinded_beacon_block(block_root, &block)
.propose_blinded_beacon_block(block_root, &block_contents)
.await
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
@@ -355,7 +351,7 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
ProvenancedPayload::Builder(full_payload)
};
Some(full_payload)
Some(full_payload_contents)
} else {
None
};
@@ -363,23 +359,14 @@ pub async fn reconstruct_block<T: BeaconChainTypes>(
match full_payload_opt {
// A block without a payload is pre-merge and we consider it locally
// built.
None => block
.deconstruct()
.0
.try_into_full_block(None)
.map(SignedBlockContents::Block)
None => block_contents
.try_into_full_block_and_blobs(None)
.map(ProvenancedBlock::local),
Some(ProvenancedPayload::Local(full_payload)) => block
.deconstruct()
.0
.try_into_full_block(Some(full_payload))
.map(SignedBlockContents::Block)
Some(ProvenancedPayload::Local(full_payload_contents)) => block_contents
.try_into_full_block_and_blobs(Some(full_payload_contents))
.map(ProvenancedBlock::local),
Some(ProvenancedPayload::Builder(full_payload)) => block
.deconstruct()
.0
.try_into_full_block(Some(full_payload))
.map(SignedBlockContents::Block)
Some(ProvenancedPayload::Builder(full_payload_contents)) => block_contents
.try_into_full_block_and_blobs(Some(full_payload_contents))
.map(ProvenancedBlock::builder),
}
.ok_or_else(|| {