Skip to content
222 changes: 201 additions & 21 deletions block_producer/src/block_producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use std::{
};

use anyhow::{Context as _, Error as AnyhowError, Result};
use bls::{AggregateSignature, PublicKeyBytes, SignatureBytes, traits::Signature as _};
use bls::{
AggregateSignature, PublicKeyBytes, SignatureBytes,
traits::{Signature as _, SignatureBytes as _},
};
use builder_api::{BuilderApi, combined::SignedBuilderBid};
use cached::{Cached as _, SizedCache};
use dedicated_executor::{DedicatedExecutor, Job};
Expand All @@ -20,7 +23,7 @@ use futures::{
lock::Mutex,
stream::{FuturesOrdered, StreamExt as _},
};
use helper_functions::{accessors, misc, predicates};
use helper_functions::{accessors, misc, predicates, verifier::NullVerifier};
use itertools::{Either, Itertools as _};
use keymanager::ProposerConfigs;
use logging::{error_with_peers, info_with_peers, warn_with_peers};
Expand Down Expand Up @@ -72,11 +75,15 @@ use types::{
ExecutionRequests,
},
fulu::containers::{BeaconBlock as FuluBeaconBlock, BeaconBlockBody as FuluBeaconBlockBody},
gloas::containers::{
BeaconBlock as GloasBeaconBlock, BeaconBlockBody as GloasBeaconBlockBody,
SignedExecutionPayloadBid,
gloas::{
consts::BUILDER_INDEX_SELF_BUILD,
containers::{
BeaconBlock as GloasBeaconBlock, BeaconBlockBody as GloasBeaconBlockBody,
ExecutionPayloadBid, ExecutionPayloadEnvelope, SignedExecutionPayloadBid,
SignedExecutionPayloadEnvelope,
},
},
nonstandard::{BlockRewards, Phase, WithBlobsAndMev},
nonstandard::{BlockRewards, Phase, WEI_IN_GWEI, WithBlobsAndMev},
phase0::{
consts::FAR_FUTURE_EPOCH,
containers::{
Expand All @@ -90,7 +97,7 @@ use types::{
},
},
preset::{Preset, SyncSubcommitteeSize},
traits::{BeaconState as _, PostBellatrixBeaconState},
traits::{BeaconState as _, PostBellatrixBeaconState, PostGloasBeaconState},
};

use crate::misc::{PayloadIdEntry, ProposerData, ValidatorBlindedBlock, build_graffiti};
Expand Down Expand Up @@ -149,6 +156,7 @@ impl<P: Preset, W: Wait> BlockProducer<P, W> {
voluntary_exits: Mutex::new(vec![]),
payload_cache: Mutex::new(SizedCache::with_size(PAYLOAD_CACHE_SIZE)),
payload_id_cache: Mutex::new(SizedCache::with_size(PAYLOAD_ID_CACHE_SIZE)),
cached_payload_roots: Mutex::new(SizedCache::with_size(PAYLOAD_CACHE_SIZE)),
metrics,
fake_execution_payloads,
});
Expand Down Expand Up @@ -623,6 +631,9 @@ struct ProducerContext<P: Preset, W: Wait> {
voluntary_exits: Mutex<Vec<SignedVoluntaryExit>>,
payload_cache: PayloadCache<P>,
payload_id_cache: Mutex<SizedCache<(H256, Slot), PayloadId>>,
// Cached payload root by `BlockBuildContext.head_block_root` for `payload_cache` retrieval
// used to construct execution payload envelope
cached_payload_roots: Mutex<SizedCache<H256, H256>>,
metrics: Option<Arc<Metrics>>,
fake_execution_payloads: bool,
}
Expand All @@ -633,6 +644,7 @@ pub struct BlockBuildOptions {
pub disable_blockprint_graffiti: bool,
pub skip_randao_verification: bool,
pub builder_boost_factor: Uint256,
pub enable_local_payload_building: bool,
}

#[derive(Clone)]
Expand Down Expand Up @@ -1150,13 +1162,19 @@ impl<P: Preset, W: Wait> BlockBuildContext<P, W> {
block_without_state_root: BeaconBlock<P>,
local_execution_payload_handle: Option<LocalExecutionPayloadJoinHandle<P>>,
) -> Result<Option<(WithBlobsAndMev<BeaconBlock<P>, P>, Option<BlockRewards>)>> {
let payload_with_data = if let Some(handle) = local_execution_payload_handle {
handle
// Start from Gloas, proposer no longer required to build execution payload data
// unless they choose to self-build, as favor or no active builders
let should_build_payload = self.beacon_state.post_gloas().is_none_or(|state| {
self.options.enable_local_payload_building
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: It looks like that using --enable-local-payload-building flag not only enables but forces self-build. If this is the feature that we want, it should probably named --force-local-payload-building or --force-self-build

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can figure out the naming later

|| accessors::get_active_builder_indices(state).count() == 0
});

let mut payload_with_data = None;
if should_build_payload && let Some(handle) = local_execution_payload_handle {
payload_with_data = handle
.await?
.map(|value| value.map(|value| value.map(Some)))
} else {
None
};
}

let WithClientVersions {
client_versions,
Expand All @@ -1166,26 +1184,65 @@ impl<P: Preset, W: Wait> BlockBuildContext<P, W> {
commitments,
proofs,
blobs,
mev,
mev: mut block_mev,
execution_requests,
},
} = match payload_with_data {
Some(payload_with_mev_and_versions) => payload_with_mev_and_versions,
None => {
if self.beacon_state.post_capella().is_some()
|| post_merge_state(&self.beacon_state).is_some()
{
let has_no_payload = if self.beacon_state.is_post_gloas() {
should_build_payload
} else {
self.beacon_state.post_capella().is_some()
|| post_merge_state(&self.beacon_state).is_some()
};

if has_no_payload {
return Err(AnyhowError::msg("no execution payload to include in block"));
}

WithClientVersions::none(WithBlobsAndMev::with_default(None))
}
};

let mut without_state_root_with_payload = block_without_state_root
.with_execution_payload(execution_payload)?
.with_blob_kzg_commitments(commitments)
.with_execution_requests(execution_requests);
let mut without_state_root_with_payload =
if let Some(state) = self.beacon_state.post_gloas() {
let signed_payload_bid = if should_build_payload {
// Cache payload root for payload envelope construction (only when self-building)
// External builders publish their own envelope
if let Some(ref payload) = execution_payload {
self.producer_context
.cached_payload_roots
.lock()
.await
.cache_set(self.head_block_root, payload.hash_tree_root());
}

self.construct_self_payload_bid(state, execution_payload, commitments)
.await?
} else {
// TODO: (gloas): select from received bids based on proposer preference
let selected_bid = self
.producer_context
.controller
.accepted_payload_bid_at_slot(state.slot());

// Set block_mev value to the in-protocol builder bid value to be compare with
// `boosted_builder_mev` in case we still want to support the off-protocol builders
block_mev = selected_bid
.as_ref()
.map(|bid| Uint256::from_u64(bid.message.value) * WEI_IN_GWEI);

selected_bid
};

block_without_state_root.with_signed_execution_payload_bid(signed_payload_bid)
} else {
block_without_state_root
.with_execution_payload(execution_payload)?
.with_blob_kzg_commitments(commitments)
.with_execution_requests(execution_requests)
};

if !self.options.disable_blockprint_graffiti {
let graffiti = build_graffiti(self.options.graffiti, client_versions);
Expand All @@ -1201,7 +1258,7 @@ impl<P: Preset, W: Wait> BlockBuildContext<P, W> {
None,
proofs,
blobs,
mev,
block_mev,
// Execution requests are moved to block.
None,
),
Expand Down Expand Up @@ -1524,6 +1581,40 @@ impl<P: Preset, W: Wait> BlockBuildContext<P, W> {
)
}

async fn construct_self_payload_bid(
&self,
state: &(impl PostGloasBeaconState<P> + ?Sized),
execution_payload_opt: Option<ExecutionPayload<P>>,
blob_kzg_commitments_opt: Option<
ContiguousList<KzgCommitment, P::MaxBlobCommitmentsPerBlock>,
>,
) -> Result<Option<SignedExecutionPayloadBid<P>>> {
let Some(payload) = execution_payload_opt else {
return Ok(None);
};

let fee_recipient = self.fee_recipient().await?;

let payload_bid = ExecutionPayloadBid {
parent_block_hash: state.latest_block_hash(),
parent_block_root: state.latest_block_header().hash_tree_root(),
block_hash: payload.block_hash(),
prev_randao: payload.prev_randao(),
fee_recipient,
gas_limit: payload.gas_limit(),
builder_index: BUILDER_INDEX_SELF_BUILD,
slot: state.slot(),
value: 0,
execution_payment: 0,
blob_kzg_commitments: blob_kzg_commitments_opt.unwrap_or_default(),
};

Ok(Some(SignedExecutionPayloadBid {
message: payload_bid,
signature: SignatureBytes::empty(),
}))
}

#[instrument(skip_all, level = "debug")]
pub async fn prepare_execution_payload_attributes(
&self,
Expand Down Expand Up @@ -1949,6 +2040,95 @@ impl<P: Preset, W: Wait> BlockBuildContext<P, W> {
.flatten()
}

pub async fn compute_execution_payload_envelope(
&self,
beacon_block_root: H256,
) -> Result<Option<ExecutionPayloadEnvelope<P>>> {
let Some((payload, execution_requests)) = self.get_gloas_envelope_data().await else {
return Ok(None);
};

// Build envelope with placeholder state_root (will be set after processing)
let message = ExecutionPayloadEnvelope {
payload,
execution_requests,
builder_index: BUILDER_INDEX_SELF_BUILD,
beacon_block_root,
slot: self.beacon_state.slot(),
state_root: H256::zero(), // Placeholder
};

let signed_envelope = SignedExecutionPayloadEnvelope {
message,
signature: SignatureBytes::default(),
};

match &*self.beacon_state {
BeaconState::Phase0(_)
| BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_)
| BeaconState::Electra(_)
| BeaconState::Fulu(_) => Err(AnyhowError::msg(
"compute_execution_payload_envelope requires post-Gloas state",
)),
BeaconState::Gloas(gloas_state) => {
let mut post_execution_state = gloas_state.clone();

gloas::process_execution_payload(
&self.producer_context.chain_config,
&self.producer_context.pubkey_cache,
&mut post_execution_state,
&signed_envelope,
&self.producer_context.execution_engine,
NullVerifier,
)?;

let SignedExecutionPayloadEnvelope { mut message, .. } = signed_envelope;

message.state_root = post_execution_state.hash_tree_root();

Ok(Some(message))
}
}
}

/// Get cached Gloas envelope data from `payload_cache` (called by validator after signing block)
/// Returns None if not self-building (external builder publishes envelope)
/// Returns only the fields needed to build `ExecutionPayloadEnvelope`
async fn get_gloas_envelope_data(
&self,
) -> Option<(DenebExecutionPayload<P>, ExecutionRequests<P>)> {
let payload_root = *self
.producer_context
.cached_payload_roots
.lock()
.await
.cache_get(&self.head_block_root)?;

let local_payload = self
.producer_context
.payload_cache
.lock()
.await
.cache_get(&payload_root)
.cloned()?;

let WithBlobsAndMev {
value: execution_payload,
execution_requests,
..
} = local_payload.result;

let ExecutionPayload::Deneb(deneb_payload) = execution_payload else {
warn_with_peers!("unexpected non-Deneb payload format in Gloas envelope data");
return None;
};

Some((deneb_payload, execution_requests.unwrap_or_default()))
}

async fn fee_recipient(&self) -> Result<ExecutionAddress> {
self.producer_context
.prepared_proposers
Expand Down
4 changes: 2 additions & 2 deletions eip_7594/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ pub async fn try_convert_to_cells_and_kzg_proofs<P: Preset>(

pub async fn construct_data_column_sidecars_from_blobs<P: Preset>(
block_or_sidecar: BlockOrDataColumnSidecar<P>,
received_blobs: Vec<Blob<P>>,
received_blobs: impl ExactSizeIterator<Item = Blob<P>>,
cells_proofs: Vec<KzgProof>,
kzg_backend: KzgBackend,
metrics: Option<Arc<Metrics>>,
Expand All @@ -470,7 +470,7 @@ pub async fn construct_data_column_sidecars_from_blobs<P: Preset>(
.map(|metrics| metrics.data_column_sidecar_computation.start_timer());

let cells_and_kzg_proofs = try_convert_to_cells_and_kzg_proofs::<P>(
received_blobs.into_iter(),
received_blobs,
&cells_proofs,
kzg_backend,
dedicated_executor,
Expand Down
8 changes: 4 additions & 4 deletions eth1_api/src/execution_blob_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,9 @@ impl<P: Preset, W: Wait> ExecutionBlobFetcher<P, W> {
let slot = block_or_sidecar.slot();
let block_root = block_or_sidecar.block_root();

// TODO: (gloas): block can be imported before data available
if self.controller.contains_block(block_root)
if self
.controller
.contains_block_and_data_available(block_root)
|| self
.controller
.is_sidecars_construction_started(&block_root)
Expand All @@ -237,7 +238,6 @@ impl<P: Preset, W: Wait> ExecutionBlobFetcher<P, W> {
return;
}

// TODO: (gloas): get `blob_kzg_commitments` from post-gloas payload envelope
let Some(kzg_commitments) = block_or_sidecar.kzg_commitments() else {
return;
};
Expand Down Expand Up @@ -333,7 +333,7 @@ impl<P: Preset, W: Wait> ExecutionBlobFetcher<P, W> {
let reconstruction_result =
eip_7594::construct_data_column_sidecars_from_blobs(
block_or_sidecar,
received_blobs,
received_blobs.into_iter(),
cells_proofs,
self.controller.store_config().kzg_backend,
self.metrics.clone(),
Expand Down
Loading
Loading