Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
72f0a7b
move commitments to bid
eserilev Jan 29, 2026
dee394c
Lint fixes
eserilev Jan 30, 2026
e1439e6
Use module level imports
eserilev Jan 30, 2026
047599a
Fix CI
eserilev Jan 30, 2026
bf1fb7e
Add testing for Gloas block body
michaelsproul Feb 3, 2026
7f51c7d
Pin nightly tests
michaelsproul Feb 3, 2026
1569123
Merge branch 'gloas-move-commitments-to-bid' into unstable
eserilev Feb 3, 2026
4c9fa24
Block and bid production
eserilev Feb 3, 2026
5bb7ebb
Merge branch 'unstable' of https://github.com/sigp/lighthouse into gl…
eserilev Feb 3, 2026
7cf4eb0
Add new block production endpoint
eserilev Feb 4, 2026
50dde15
Add payload to a cache for later signing
eserilev Feb 4, 2026
2d321f6
Add get payload envelope route
eserilev Feb 4, 2026
1ed80fa
Fetch and sign payload envelope
eserilev Feb 4, 2026
2585384
Publish payload
eserilev Feb 4, 2026
1c1e6dd
Fmt
eserilev Feb 4, 2026
1e69734
resolve merge conflicts
eserilev Feb 4, 2026
96d02ad
Resolve merge conflicts
eserilev Feb 4, 2026
75bb428
Resolve some TODOs
eserilev Feb 4, 2026
f9bfaf9
Move block production to gloas file (no logic change).
jimmygchen Feb 4, 2026
339ba6e
Move gloas http logic to modules.
jimmygchen Feb 5, 2026
62f9648
FMT
eserilev Feb 5, 2026
0a098f2
Remove unecessary fields
eserilev Feb 10, 2026
a311b1a
Use local builder index const
eserilev Feb 10, 2026
081efc7
Fix sig domain
eserilev Feb 10, 2026
08c4653
Update beacon_node/http_api/src/validator/execution_payload_bid.rs
eserilev Feb 10, 2026
47500e2
Merge branch 'gloas-block-and-bid-production' of https://github.com/e…
eserilev Feb 10, 2026
8a53da9
Add unexpected error variant
eserilev Feb 10, 2026
525bed7
add consts
eserilev Feb 10, 2026
ed5cc3b
add const
eserilev Feb 10, 2026
3eb4db2
Instrument
eserilev Feb 10, 2026
850dea6
Remove unused
eserilev Feb 10, 2026
fea43fb
Move block production specific stuff to block_production module
eserilev Feb 10, 2026
8eb409a
linting
eserilev Feb 10, 2026
34b4c46
Reorder
eserilev Feb 10, 2026
aa07952
Add comment
eserilev Feb 10, 2026
9917e22
Revert
eserilev Feb 10, 2026
b16da14
Fix test
eserilev Feb 10, 2026
088e5bb
Remove unused endpoint
eserilev Feb 10, 2026
36f722c
Merge branch 'unstable' of https://github.com/sigp/lighthouse into gl…
eserilev Feb 10, 2026
846b1ba
pub crate
eserilev Feb 10, 2026
ac51041
Fix state root
eserilev Feb 10, 2026
a08c1d9
call process_envelope
eserilev Feb 10, 2026
8d85350
Merge branch 'unstable' of https://github.com/sigp/lighthouse into gl…
eserilev Feb 11, 2026
d140145
Fix
eserilev Feb 14, 2026
9bbcfe3
Separate payload construction for gloas
eserilev Feb 14, 2026
4795e1f
Merge branch 'unstable' of https://github.com/sigp/lighthouse into gl…
eserilev Feb 14, 2026
9db9965
Revert
eserilev Feb 14, 2026
022ee4e
Special case signed beacon block ssz decoding
eserilev Feb 14, 2026
75014fe
SseExtendPayloadAttributes hack
eserilev Feb 14, 2026
90f758c
Comments
eserilev Feb 14, 2026
f7d58ae
Comments
eserilev Feb 14, 2026
9b7c21d
Comments
eserilev Feb 14, 2026
ff83c1e
Fix build
jimmygchen Feb 16, 2026
7948b62
Merge remote-tracking branch 'origin/unstable' into gloas-block-and-b…
jimmygchen Feb 16, 2026
4420550
Fix arbitrary check
jimmygchen Feb 16, 2026
e8e25b8
Merge branch 'unstable' of https://github.com/sigp/lighthouse into gl…
eserilev Feb 16, 2026
b4a8e9a
Use produceblockv4metadata
eserilev Feb 16, 2026
12bbc5b
Fix envelope signing in test
eserilev Feb 16, 2026
21738c0
Gloas not implemented accepts string
eserilev Feb 16, 2026
8045d7d
Fmt
eserilev Feb 16, 2026
e9083e3
Add TODO
eserilev Feb 16, 2026
988c53e
yeet some code
eserilev Feb 16, 2026
a7c556d
fmt
eserilev Feb 16, 2026
621bd9f
pass by ref
eserilev Feb 16, 2026
7226bba
Fix web3 signing logic
eserilev Feb 16, 2026
5442281
align endpoint requests
eserilev Feb 16, 2026
107777c
Collapse into one
eserilev Feb 16, 2026
e8916ec
remove double clones
eserilev Feb 16, 2026
0c00ee1
Add inline comment'
eserilev Feb 16, 2026
c0ac229
Move custom consts
eserilev Feb 16, 2026
54bf772
add ssz method for POST payload
eserilev Feb 16, 2026
f70c3f7
remove clone
eserilev Feb 16, 2026
24be66e
extract common code
eserilev Feb 16, 2026
857d6ad
Default to ssz in block service for payload envelope stuff
eserilev Feb 16, 2026
489ef00
use fork versioned response
eserilev Feb 16, 2026
ec4b05d
Address remaining minor comments
jimmygchen Feb 17, 2026
8bbcdf4
Replace hard coded fork names.
jimmygchen Feb 17, 2026
042f88f
Merge branch 'unstable' into gloas-block-and-bid-production
jimmygchen Feb 17, 2026
9f6ef6e
Add comment to builder application domain.
jimmygchen Feb 17, 2026
30d18c9
Merge branch 'gloas-block-and-bid-production' of github.com:eserilev/…
jimmygchen Feb 17, 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
257 changes: 36 additions & 221 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::errors::{BeaconChainError as Error, BlockProductionError};
use crate::events::ServerSentEventHandler;
use crate::execution_payload::{NotifyExecutionLayer, PreparePayloadHandle, get_execution_payload};
use crate::fetch_blobs::EngineGetBlobsOutput;
use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult};
use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx};
use crate::graffiti_calculator::{GraffitiCalculator, GraffitiSettings};
use crate::kzg_utils::reconstruct_blobs;
use crate::light_client_finality_update_verification::{
Expand All @@ -56,6 +56,7 @@ use crate::observed_block_producers::ObservedBlockProducers;
use crate::observed_data_sidecars::ObservedDataSidecars;
use crate::observed_operations::{ObservationOutcome, ObservedOperations};
use crate::observed_slashable::ObservedSlashable;
use crate::pending_payload_envelopes::PendingPayloadEnvelopes;
use crate::persisted_beacon_chain::PersistedBeaconChain;
use crate::persisted_custody::persist_custody_context;
use crate::persisted_fork_choice::PersistedForkChoice;
Expand Down Expand Up @@ -235,7 +236,7 @@ pub struct PrePayloadAttributes {
///
/// The parent block number is not part of the payload attributes sent to the EL, but *is*
/// sent to builders via SSE.
pub parent_block_number: u64,
pub parent_block_number: Option<u64>,
/// The block root of the block being built upon (same block as fcU `headBlockHash`).
pub parent_beacon_block_root: Hash256,
}
Expand Down Expand Up @@ -419,6 +420,9 @@ pub struct BeaconChain<T: BeaconChainTypes> {
RwLock<ObservedDataSidecars<DataColumnSidecar<T::EthSpec>, T::EthSpec>>,
/// Maintains a record of slashable message seen over the gossip network or RPC.
pub observed_slashable: RwLock<ObservedSlashable<T::EthSpec>>,
/// Cache of pending execution payload envelopes for local block building.
/// Envelopes are stored here during block production and eventually published.
pub pending_payload_envelopes: RwLock<PendingPayloadEnvelopes<T::EthSpec>>,
/// Maintains a record of which validators have submitted voluntary exits.
pub observed_voluntary_exits: Mutex<ObservedOperations<SignedVoluntaryExit, T::EthSpec>>,
/// Maintains a record of which validators we've seen proposer slashings for.
Expand Down Expand Up @@ -4504,55 +4508,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(())
}

/// If configured, wait for the fork choice run at the start of the slot to complete.
#[instrument(level = "debug", skip_all)]
fn wait_for_fork_choice_before_block_production(
self: &Arc<Self>,
slot: Slot,
) -> Result<(), BlockProductionError> {
if let Some(rx) = &self.fork_choice_signal_rx {
let current_slot = self
.slot()
.map_err(|_| BlockProductionError::UnableToReadSlot)?;

let timeout = Duration::from_millis(self.config.fork_choice_before_proposal_timeout_ms);

if slot == current_slot || slot == current_slot + 1 {
match rx.wait_for_fork_choice(slot, timeout) {
ForkChoiceWaitResult::Success(fc_slot) => {
debug!(
%slot,
fork_choice_slot = %fc_slot,
"Fork choice successfully updated before block production"
);
}
ForkChoiceWaitResult::Behind(fc_slot) => {
warn!(
fork_choice_slot = %fc_slot,
%slot,
message = "this block may be orphaned",
"Fork choice notifier out of sync with block production"
);
}
ForkChoiceWaitResult::TimeOut => {
warn!(
message = "this block may be orphaned",
"Timed out waiting for fork choice before proposal"
);
}
}
} else {
error!(
%slot,
%current_slot,
message = "check clock sync, this block may be orphaned",
"Producing block at incorrect slot"
);
}
}
Ok(())
}

pub async fn produce_block_with_verification(
self: &Arc<Self>,
randao_reveal: Signature,
Expand Down Expand Up @@ -4599,165 +4554,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.await
}

/// Load a beacon state from the database for block production. This is a long-running process
/// that should not be performed in an `async` context.
fn load_state_for_block_production(
self: &Arc<Self>,
slot: Slot,
) -> Result<(BeaconState<T::EthSpec>, Option<Hash256>), BlockProductionError> {
let fork_choice_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_FORK_CHOICE_TIMES);
self.wait_for_fork_choice_before_block_production(slot)?;
drop(fork_choice_timer);

let state_load_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_STATE_LOAD_TIMES);

// Atomically read some values from the head whilst avoiding holding cached head `Arc` any
// longer than necessary.
let (head_slot, head_block_root, head_state_root) = {
let head = self.canonical_head.cached_head();
(
head.head_slot(),
head.head_block_root(),
head.head_state_root(),
)
};
let (state, state_root_opt) = if head_slot < slot {
// Attempt an aggressive re-org if configured and the conditions are right.
if let Some((re_org_state, re_org_state_root)) =
self.get_state_for_re_org(slot, head_slot, head_block_root)
{
info!(
%slot,
head_to_reorg = %head_block_root,
"Proposing block to re-org current head"
);
(re_org_state, Some(re_org_state_root))
} else {
// Fetch the head state advanced through to `slot`, which should be present in the
// state cache thanks to the state advance timer.
let (state_root, state) = self
.store
.get_advanced_hot_state(head_block_root, slot, head_state_root)
.map_err(BlockProductionError::FailedToLoadState)?
.ok_or(BlockProductionError::UnableToProduceAtSlot(slot))?;
(state, Some(state_root))
}
} else {
warn!(
message = "this block is more likely to be orphaned",
%slot,
"Producing block that conflicts with head"
);
let state = self
.state_at_slot(slot - 1, StateSkipConfig::WithStateRoots)
.map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?;

(state, None)
};

drop(state_load_timer);

Ok((state, state_root_opt))
}

/// Fetch the beacon state to use for producing a block if a 1-slot proposer re-org is viable.
///
/// This function will return `None` if proposer re-orgs are disabled.
#[instrument(skip_all, level = "debug")]
fn get_state_for_re_org(
&self,
slot: Slot,
head_slot: Slot,
canonical_head: Hash256,
) -> Option<(BeaconState<T::EthSpec>, Hash256)> {
let re_org_head_threshold = self.config.re_org_head_threshold?;
let re_org_parent_threshold = self.config.re_org_parent_threshold?;

if self.spec.proposer_score_boost.is_none() {
warn!(
reason = "this network does not have proposer boost enabled",
"Ignoring proposer re-org configuration"
);
return None;
}

let slot_delay = self
.slot_clock
.seconds_from_current_slot_start()
.or_else(|| {
warn!(error = "unable to read slot clock", "Not attempting re-org");
None
})?;

// Attempt a proposer re-org if:
//
// 1. It seems we have time to propagate and still receive the proposer boost.
// 2. The current head block was seen late.
// 3. The `get_proposer_head` conditions from fork choice pass.
let proposing_on_time =
slot_delay < self.config.re_org_cutoff(self.spec.get_slot_duration());
if !proposing_on_time {
debug!(reason = "not proposing on time", "Not attempting re-org");
return None;
}

let head_late = self.block_observed_after_attestation_deadline(canonical_head, head_slot);
if !head_late {
debug!(reason = "head not late", "Not attempting re-org");
return None;
}

// Is the current head weak and appropriate for re-orging?
let proposer_head_timer =
metrics::start_timer(&metrics::BLOCK_PRODUCTION_GET_PROPOSER_HEAD_TIMES);
let proposer_head = self
.canonical_head
.fork_choice_read_lock()
.get_proposer_head(
slot,
canonical_head,
re_org_head_threshold,
re_org_parent_threshold,
&self.config.re_org_disallowed_offsets,
self.config.re_org_max_epochs_since_finalization,
)
.map_err(|e| match e {
ProposerHeadError::DoNotReOrg(reason) => {
debug!(
%reason,
"Not attempting re-org"
);
}
ProposerHeadError::Error(e) => {
warn!(
error = ?e,
"Not attempting re-org"
);
}
})
.ok()?;
drop(proposer_head_timer);
let re_org_parent_block = proposer_head.parent_node.root;

let (state_root, state) = self
.store
.get_advanced_hot_state_from_cache(re_org_parent_block, slot)
.or_else(|| {
warn!(reason = "no state in cache", "Not attempting re-org");
None
})?;

info!(
weak_head = ?canonical_head,
parent = ?re_org_parent_block,
head_weight = proposer_head.head_node.weight,
threshold_weight = proposer_head.re_org_head_weight_threshold,
"Attempting re-org due to weak head"
);

Some((state, state_root))
}

/// Get the proposer index and `prev_randao` value for a proposal at slot `proposal_slot`.
///
/// The `proposer_head` may be the head block of `cached_head` or its parent. An error will
Expand Down Expand Up @@ -4840,15 +4636,25 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
return Ok(None);
};

// Get the `prev_randao` and parent block number.
let head_block_number = cached_head.head_block_number()?;
let (prev_randao, parent_block_number) = if proposer_head == head_parent_block_root {
(
cached_head.parent_random()?,
head_block_number.saturating_sub(1),
)
// TODO(gloas) not sure what to do here see this issue
// https://github.com/sigp/lighthouse/issues/8817
let (prev_randao, parent_block_number) = if self
.spec
.fork_name_at_slot::<T::EthSpec>(proposal_slot)
.gloas_enabled()
{
(cached_head.head_random()?, None)
} else {
(cached_head.head_random()?, head_block_number)
// Get the `prev_randao` and parent block number.
let head_block_number = cached_head.head_block_number()?;
if proposer_head == head_parent_block_root {
(
cached_head.parent_random()?,
Some(head_block_number.saturating_sub(1)),
)
} else {
(cached_head.head_random()?, Some(head_block_number))
}
};

Ok(Some(PrePayloadAttributes {
Expand Down Expand Up @@ -5093,7 +4899,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}

/// Check if the block with `block_root` was observed after the attestation deadline of `slot`.
fn block_observed_after_attestation_deadline(&self, block_root: Hash256, slot: Slot) -> bool {
pub(crate) fn block_observed_after_attestation_deadline(
&self,
block_root: Hash256,
slot: Slot,
) -> bool {
let block_delays = self.block_times_cache.read().get_block_delays(
block_root,
self.slot_clock
Expand Down Expand Up @@ -5860,7 +5670,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
execution_payload_value,
)
}
BeaconState::Gloas(_) => return Err(BlockProductionError::GloasNotImplemented),
BeaconState::Gloas(_) => {
return Err(BlockProductionError::GloasNotImplemented(
"Attempting to produce gloas beacn block via non gloas code path".to_owned(),
));
}
};

let block = SignedBeaconBlock::from_block(
Expand Down Expand Up @@ -6198,13 +6012,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// Push a server-sent event (probably to a block builder or relay).
if let Some(event_handler) = &self.event_handler
&& event_handler.has_payload_attributes_subscribers()
&& let Some(parent_block_number) = pre_payload_attributes.parent_block_number
{
event_handler.register(EventKind::PayloadAttributes(ForkVersionedResponse {
data: SseExtendedPayloadAttributes {
proposal_slot: prepare_slot,
proposer_index: proposer,
parent_block_root: head_root,
parent_block_number: pre_payload_attributes.parent_block_number,
parent_block_number,
parent_block_hash: forkchoice_update_params.head_hash.unwrap_or_default(),
payload_attributes: payload_attributes.into(),
},
Expand Down
Loading