Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cf39f64
stateless endpoint impl
eserilev Jul 2, 2026
3df7409
impl stateless block building API
eserilev Jul 2, 2026
1e3070f
fixes
eserilev Jul 3, 2026
de551e2
Merge branch 'unstable' of https://github.com/sigp/lighthouse into st…
eserilev Jul 3, 2026
b160ef7
remove uneeded changes
eserilev Jul 3, 2026
63c9834
Fix comments
eserilev Jul 4, 2026
0bfca3f
Merge branch 'unstable' of https://github.com/sigp/lighthouse into st…
eserilev Jul 4, 2026
4a99a8b
Backwards compat
eserilev Jul 4, 2026
e715022
Fix
eserilev Jul 4, 2026
0daf331
Merge branch 'unstable' of https://github.com/sigp/lighthouse into st…
eserilev Jul 4, 2026
bcab92d
Fix
eserilev Jul 5, 2026
bdc7a81
Resolve merge conflicts
eserilev Jul 14, 2026
90ed419
Resolve conflicts
eserilev Jul 14, 2026
8b99450
Merge branch 'unstable' of https://github.com/sigp/lighthouse into st…
eserilev Jul 20, 2026
636ca53
Resolve merge conflicts
eserilev Jul 20, 2026
064618f
Resolve merge conflicts
eserilev Jul 20, 2026
f0c7267
Small fixes
eserilev Jul 20, 2026
23027da
Add not while syncing filter
eserilev Jul 20, 2026
f540c83
filter on envelope slot
eserilev Jul 20, 2026
ad953e9
fixes and deletions
eserilev Jul 20, 2026
ee8f8dc
more clean upo
eserilev Jul 20, 2026
8ecfb1c
Cleanup
eserilev Jul 20, 2026
529714a
Rename
eserilev Jul 20, 2026
f4e8dbd
Fix bad comments
eserilev Jul 20, 2026
ac56e91
More bogus comments
eserilev Jul 20, 2026
8db531d
Merge test cases
eserilev Jul 20, 2026
b462c1c
Resolve merge conflicts
eserilev Jul 31, 2026
e4d5f6f
FMT
eserilev Jul 31, 2026
046f3dc
Add validation level
eserilev Jul 31, 2026
1fe26e2
Merge branch 'unstable' into stateless-block-building-api
eserilev Aug 1, 2026
4154b6a
FIx test
eserilev Aug 1, 2026
fe12eb3
Merge branch 'stateless-block-building-api' of https://github.com/ese…
eserilev Aug 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7034,6 +7034,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.envelope_times_cache.write().prune(slot);
self.gossip_verified_payload_bid_cache.prune(slot);
self.gossip_verified_proposer_preferences_cache.prune(slot);
self.pending_payload_envelopes.write().prune(slot);

// Don't run heavy-weight tasks during sync.
if self.best_slot() + MAX_PER_SLOT_FORK_CHOICE_DISTANCE < slot {
Expand Down
52 changes: 36 additions & 16 deletions beacon_node/beacon_chain/src/block_production/gloas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ use tree_hash::TreeHash;
use types::consts::gloas::BUILDER_INDEX_SELF_BUILD;
use types::{
Address, Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra,
BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BeaconStateError,
BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BeaconStateError, BlobsList,
BuilderIndex, ChainSpec, Deposit, Eth1Data, EthSpec, ExecutionBlockHash, ExecutionPayloadBid,
ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequestsGloas, FullPayload, Graffiti,
Hash256, PayloadAttestation, ProposerSlashing, RelativeEpoch, SignedBeaconBlock,
Hash256, KzgProofs, PayloadAttestation, ProposerSlashing, RelativeEpoch, SignedBeaconBlock,
SignedBlsToExecutionChange, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope,
SignedVoluntaryExit, Slot, SyncAggregate, Uint256, Withdrawal, Withdrawals,
};
Expand All @@ -48,14 +48,23 @@ pub const BID_VALUE_SELF_BUILD: u64 = 0;
pub const EXECUTION_PAYMENT_TRUSTLESS_BUILD: u64 = 0;

type ConsensusBlockValue = u64;

pub type PayloadEnvelopeContents<E> = (
Arc<ExecutionPayloadEnvelope<E>>,
KzgProofs<E>,
Arc<BlobsList<E>>,
);

/// Execution payload value in wei: the local payload's EL value when self-building, or the
/// bid value when committing to a builder bid.
type ExecutionPayloadValue = Uint256;

type BlockProductionResult<E> = (
BeaconBlock<E>,
BeaconState<E>,
ConsensusBlockValue,
ExecutionPayloadValue,
Option<PayloadEnvelopeContents<E>>,
);

pub type PreparePayloadResult<E> = Result<BlockProposalContentsGloas<E>, BlockProductionError>;
Expand Down Expand Up @@ -537,11 +546,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {

/// Complete a block by computing its state root, and
///
/// Return `(block, post_block_state, consensus_block_value, execution_payload_value)` where:
/// Return `(block, post_block_state, consensus_block_value, execution_payload_value,
/// payload_contents)` where:
///
/// - `post_block_state` is the state post block application
/// - `consensus_block_value` is the consensus-layer rewards for `block`
/// - `execution_payload_value` is the wei value of the winning payload bid
/// - `payload_contents` is the locally-built envelope, KZG proofs and blobs (`None` when
/// committing to a builder bid)
#[instrument(skip_all, level = "debug")]
fn complete_partial_beacon_block_gloas(
&self,
Expand Down Expand Up @@ -682,7 +694,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// Construct and cache the ExecutionPayloadEnvelope if we have payload data.
// For local building, we always have payload data.
// For trustless building, the builder will provide the envelope separately.
if let Some(payload_data) = payload_data {
let payload_contents = if let Some(payload_data) = payload_data {
let beacon_block_root = block.tree_hash_root();
let parent_beacon_block_root = block.parent_root();
let execution_payload_envelope = ExecutionPayloadEnvelope {
Expand Down Expand Up @@ -710,23 +722,25 @@ impl<T: BeaconChainTypes> BeaconChain<T> {

// Cache the envelope for later retrieval by the validator for signing and publishing.
let envelope_slot = payload_data.slot;
// TODO(gloas) might be safer to cache by root instead of by slot.
// We should revisit this once this code path + beacon api spec matures
let (blobs, _) = payload_data.blobs_and_proofs;
self.pending_payload_envelopes.write().insert(
envelope_slot,
PendingEnvelopeData {
envelope: signed_envelope.message,
blobs: Some(blobs),
},
);
let (blobs, kzg_proofs) = payload_data.blobs_and_proofs;
let envelope = Arc::new(signed_envelope.message);
let blobs = Arc::new(blobs);
self.pending_payload_envelopes
.write()
.insert(PendingEnvelopeData {
envelope: envelope.clone(),
blobs: Some(blobs.clone()),
});

debug!(
%beacon_block_root,
slot = %envelope_slot,
"Cached pending execution payload envelope"
);
}
Some((envelope, kzg_proofs, blobs))
} else {
None
};

metrics::inc_counter(&metrics::BLOCK_PRODUCTION_SUCCESSES);

Expand All @@ -737,7 +751,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
"Produced beacon block"
);

Ok((block, state, consensus_block_value, execution_payload_value))
Ok((
block,
state,
consensus_block_value,
execution_payload_value,
payload_contents,
))
}

/// Produce a self-build `ExecutionPayloadBid` for some `slot` upon the given `state`.
Expand Down
2 changes: 2 additions & 0 deletions beacon_node/beacon_chain/src/block_production/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use crate::{

mod gloas;

pub use gloas::PayloadEnvelopeContents;

/// State loaded from the database for block production.
pub(crate) struct BlockProductionState<E: EthSpec> {
pub state: BeaconState<E>,
Expand Down
97 changes: 65 additions & 32 deletions beacon_node/beacon_chain/src/kzg_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,38 +292,8 @@ pub fn blobs_to_data_column_sidecars<E: EthSpec>(
.map_err(|_err| DataColumnSidecarError::PreDeneb)?;
let signed_block_header = block.signed_block_header();

if cell_proofs.len() != blobs.len() * E::number_of_columns() {
return Err(DataColumnSidecarError::InvalidCellProofLength {
expected: blobs.len() * E::number_of_columns(),
actual: cell_proofs.len(),
});
}

let proof_chunks = cell_proofs
.chunks_exact(E::number_of_columns())
.collect::<Vec<_>>();

// NOTE: assumes blob sidecars are ordered by index
let zipped: Vec<_> = blobs.iter().zip(proof_chunks).collect();
let blob_cells_and_proofs_vec = zipped
.into_par_iter()
.map(|(blob, proofs)| {
let blob = blob.as_ref().try_into().map_err(|e| {
KzgError::InconsistentArrayLength(format!(
"blob should have a guaranteed size due to FixedVector: {e:?}"
))
})?;

kzg.compute_cells(blob).and_then(|cells| {
let proofs = proofs.try_into().map_err(|e| {
KzgError::InconsistentArrayLength(format!(
"proof chunks should have exactly `number_of_columns` proofs: {e:?}"
))
})?;
Ok((cells, proofs))
})
})
.collect::<Result<Vec<_>, KzgError>>()?;
let blob_cells_and_proofs_vec =
compute_cells_with_provided_proofs::<E>(blobs, cell_proofs, kzg)?;

if block.fork_name_unchecked().gloas_enabled() {
build_data_column_sidecars_gloas(
Expand Down Expand Up @@ -376,6 +346,69 @@ pub fn blobs_to_data_column_sidecars_gloas<E: EthSpec>(
.map_err(DataColumnSidecarError::BuildSidecarFailed)
}

/// Build Gloas data column sidecars from blobs and pre-computed cell proofs, computing only the
/// cells locally.
pub fn blobs_to_data_column_sidecars_gloas_with_proofs<E: EthSpec>(
blobs: &[&Blob<E>],
cell_proofs: Vec<KzgProof>,
beacon_block_root: Hash256,
slot: Slot,
kzg: &Kzg,
spec: &ChainSpec,
) -> Result<DataColumnSidecarList<E>, DataColumnSidecarError> {
if blobs.is_empty() {
return Ok(vec![]);
}

let blob_cells_and_proofs_vec =
compute_cells_with_provided_proofs::<E>(blobs, cell_proofs, kzg)?;

build_data_column_sidecars_gloas(beacon_block_root, slot, blob_cells_and_proofs_vec, spec)
.map_err(DataColumnSidecarError::BuildSidecarFailed)
}

/// Compute cells for each blob and pair them with the provided per-blob cell proofs.
fn compute_cells_with_provided_proofs<E: EthSpec>(
blobs: &[&Blob<E>],
cell_proofs: Vec<KzgProof>,
kzg: &Kzg,
) -> Result<Vec<CellsAndKzgProofs>, DataColumnSidecarError> {
if cell_proofs.len() != blobs.len() * E::number_of_columns() {
return Err(DataColumnSidecarError::InvalidCellProofLength {
expected: blobs.len() * E::number_of_columns(),
actual: cell_proofs.len(),
});
}

let proof_chunks = cell_proofs
.chunks_exact(E::number_of_columns())
.collect::<Vec<_>>();

// NOTE: assumes blobs and proofs are ordered by blob index
let zipped: Vec<_> = blobs.iter().zip(proof_chunks).collect();
let blob_cells_and_proofs_vec = zipped
.into_par_iter()
.map(|(blob, proofs)| {
let blob = blob.as_ref().try_into().map_err(|e| {
KzgError::InconsistentArrayLength(format!(
"blob should have a guaranteed size due to FixedVector: {e:?}"
))
})?;

kzg.compute_cells(blob).and_then(|cells| {
let proofs = proofs.try_into().map_err(|e| {
KzgError::InconsistentArrayLength(format!(
"proof chunks should have exactly `number_of_columns` proofs: {e:?}"
))
})?;
Ok((cells, proofs))
})
})
.collect::<Result<Vec<_>, KzgError>>()?;

Ok(blob_cells_and_proofs_vec)
}

/// Build data column sidecars from a signed beacon block and its blobs.
#[instrument(skip_all, level = "debug", fields(blob_count = blobs_and_proofs.len()))]
pub fn blobs_to_partial_data_columns<E: EthSpec>(
Expand Down
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub use self::beacon_chain::{
ProduceBlockVerification, StateSkipConfig, WhenSlotSkipped,
};
pub use self::beacon_snapshot::BeaconSnapshot;
pub use self::block_production::PayloadEnvelopeContents;
pub use self::chain_config::ChainConfig;
pub use self::errors::{BeaconChainError, BlockProductionError};
pub use self::historical_blocks::HistoricalBlockError;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::assert_matches;
use std::sync::Arc;
use std::time::Duration;

Expand Down Expand Up @@ -413,7 +412,10 @@ fn invalid_bid_slot() {
ctx.genesis_block_root,
);
let result = GossipVerifiedPayloadBid::new(bid, &gossip);
assert_matches!(result, Err(PayloadBidError::InvalidBidSlot { .. }));
assert!(matches!(
result,
Err(PayloadBidError::InvalidBidSlot { .. })
));
}

#[test]
Expand Down
Loading
Loading