Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
15 changes: 11 additions & 4 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4998,16 +4998,23 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
return Ok(None);
};

// 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)
// If the non-finalized portion of chain has no canonical payload, the latest payload is
// the finalized one. Fork choice has pruned the block root associated with the finalized
// payload, so we fetch the relevant data from `PayloadInfo`. If a gloas payload envelope
// has ever been revealed, fall back to walking to the last pre-Gloas block on the head's chain.
(
cached_head.head_random()?,
cached_head
.head_block_number_gloas()
.or(self.store.get_payload_info().latest_finalized_block_number)
.or(self.latest_pre_gloas_block_number(cached_head.head_block_root())?),
)
} else {
// Get the `prev_randao` and parent block number.
let head_block_number = cached_head.head_block_number()?;
if proposer_head == head_parent_block_root {
(
Expand Down
23 changes: 23 additions & 0 deletions beacon_node/beacon_chain/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,29 @@ where
.get_payload_envelope(&head_block_root)
.map_err(|e| format!("Error loading head execution envelope: {:?}", e))?
.map(Arc::new)
} else if self
.spec
.fork_name_at_slot::<E>(head_block.slot())
.gloas_enabled()
{
let latest_full_block_root_opt = fork_choice
.latest_parent_full_block(head_block_root, &self.spec)
.map_err(|e| {
format!(
"Error fetching latest full beacon block root from fork choice: {:?}",
e
)
})?;

if let Some(latest_full_block_root) = latest_full_block_root_opt {
store
.get_payload_envelope(&latest_full_block_root)
.map_err(|e| format!("Error loading latest execution envelope: {:?}", e))?
.map(Arc::new)
} else {
// No payload revealed since finalization.
None
}
} else {
None
};
Expand Down
150 changes: 149 additions & 1 deletion beacon_node/beacon_chain/src/canonical_head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ use std::sync::Arc;
use std::time::Duration;
use store::{
Error as StoreError, KeyValueStore, KeyValueStoreOp, StoreConfig, iter::StateRootsIterator,
metadata::PayloadInfo,
};
use task_executor::{JoinHandle, ShutdownReason};
use tracing::{debug, error, info, instrument, warn};
Expand Down Expand Up @@ -178,7 +179,7 @@ impl<E: EthSpec> CachedHead<E> {

/// Returns the execution block number of the block at the head of the chain.
///
/// Returns an error if the chain is prior to Bellatrix.
/// Returns an error if the chain is prior to Bellatrix or post-Gloas
pub fn head_block_number(&self) -> Result<u64, BeaconStateError> {
self.snapshot
.beacon_block
Expand All @@ -187,6 +188,26 @@ impl<E: EthSpec> CachedHead<E> {
.map(|payload| payload.block_number())
}

/// Returns the execution block number of the block at the head of the chain.
///
/// Returns `None` if the chain is prior to Gloas.
pub fn head_block_number_gloas(&self) -> Option<u64> {
if let Some(head_block_number) = self
.snapshot
.execution_envelope
.as_ref()
.map(|envelope| envelope.message.payload.block_number)
{
Some(head_block_number)
} else {
// This fallback is strictly for the fork boundary case when self.snapshot.execution_envelope is `None`.
// TODO(gloas) If there is a missed/orphaned envelope at the fork boundary we wont be able to get the block number using this fallback.
// We might want to try handling that edge case. Returning `None` here means that we don't emit a payload attributes SSE event which
// might be important for upstream consumers (i.e. the builder client).
self.head_block_number().ok()
}
}

/// Returns the active validator count for the current epoch of the head state.
///
/// Should only return `None` if the caches have not been built on the head state (this should
Expand Down Expand Up @@ -324,6 +345,21 @@ impl<T: BeaconChainTypes> CanonicalHead<T> {
store
.get_payload_envelope(&beacon_block_root)?
.map(Arc::new)
} else if spec
.fork_name_at_slot::<T::EthSpec>(beacon_block.slot())
.gloas_enabled()
{
let latest_full_block_root_opt =
fork_choice.latest_parent_full_block(beacon_block_root, spec)?;

if let Some(latest_full_block_root) = latest_full_block_root_opt {
store
.get_payload_envelope(&latest_full_block_root)?
.map(Arc::new)
} else {
// No payload revealed since finalization.
None
}
} else {
None
};
Expand Down Expand Up @@ -723,9 +759,33 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
))?;

Some(envelope)
} else if self
.spec
.fork_name_at_slot::<T::EthSpec>(beacon_block.slot())
.gloas_enabled()
{
let fork_choice = self.canonical_head.fork_choice_read_lock();
let latest_full_block_root_opt = fork_choice
.latest_parent_full_block(new_view.head_block_root, &self.spec)?;
drop(fork_choice);

if let Some(latest_full_block_root) = latest_full_block_root_opt {
let envelope = self
.store
.get_payload_envelope(&latest_full_block_root)?
.map(Arc::new)
.ok_or(Error::MissingExecutionPayloadEnvelope(
latest_full_block_root,
))?;
Some(envelope)
} else {
// No payload revealed since finalization.
None
}
} else {
None
};

let (_, beacon_state) = self
.store
.get_advanced_hot_state(new_view.head_block_root, current_slot, state_root)?
Expand Down Expand Up @@ -998,6 +1058,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
error!(error = ?e, "Failed to prune pending payload cache on finalization");
}
}

if let Some(gloas_fork_epoch) = self.spec.gloas_fork_epoch
&& new_view.finalized_checkpoint.epoch >= gloas_fork_epoch
&& let Err(e) =
self.update_finalized_payload_info(new_view.finalized_checkpoint.root)
{
error!(error = ?e, "Failed to update finalized payload info");
}
}

if let Some(event_handler) = self.event_handler.as_ref()
Expand Down Expand Up @@ -1076,6 +1144,86 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(())
}

/// Record the block number of the latest finalized execution payload.
///
/// The payload of the finalized block isn't finalized, so we fetch the payload
/// that the finalized block builds upon.
fn update_finalized_payload_info(
self: &Arc<Self>,
finalized_block_root: Hash256,
) -> Result<(), Error> {
let latest_full_block_root = {
let fork_choice = self.canonical_head.fork_choice_read_lock();
fork_choice.latest_parent_full_block(finalized_block_root, &self.spec)?
};

let Some(latest_full_block_root) = latest_full_block_root else {
// A gloas payload envelope has never been revealed AND we have finalized a gloas epoch.
// The latest execution payload is from the last pre-gloas block. At this point that
// pre-gloas block is finalized so we set `PayloadInfo.latest_finalized_block_number`
// with its block number.
let payload_info = self.store.get_payload_info();
if payload_info.latest_finalized_block_number.is_none()
&& let Some(block_number) =
self.latest_pre_gloas_block_number(finalized_block_root)?
{
let new_payload_info = PayloadInfo {
latest_finalized_block_number: Some(block_number),
};
self.store
.compare_and_set_payload_info_with_write(payload_info, new_payload_info)?;
}
return Ok(());
};
let Some(envelope) = self.store.get_payload_envelope(&latest_full_block_root)? else {
return Ok(());
};

let payload_info = PayloadInfo {
latest_finalized_block_number: Some(envelope.message.payload.block_number),
};
self.store
.compare_and_set_payload_info_with_write(self.store.get_payload_info(), payload_info)?;

Ok(())
}

/// Walk back from `block_root` to the most recent pre-Gloas block and return its execution
/// block number.
///
/// Note: This function is only relevant between the gloas fork boundary and before a
/// gloas epoch finalized.
pub(crate) fn latest_pre_gloas_block_number(
&self,
block_root: Hash256,
) -> Result<Option<u64>, Error> {
let latest_pre_gloas_root = {
let fork_choice = self.canonical_head.fork_choice_read_lock();
fork_choice
.proto_array()
.iter_block_roots(&block_root)
.find(|(_, slot)| {
!self
.spec
.fork_name_at_slot::<T::EthSpec>(*slot)
.gloas_enabled()
})
.map(|(root, _)| root)
};

let Some(latest_pre_gloas_root) = latest_pre_gloas_root else {
return Ok(None);
};
let Some(block) = self.get_blinded_block(&latest_pre_gloas_root)? else {
return Ok(None);
};
Ok(block
.message()
.execution_payload()
.ok()
.map(|payload| payload.block_number()))
}

/// Persist fork choice to disk, writing immediately.
pub fn persist_fork_choice(&self) -> Result<(), Error> {
let _fork_choice_timer = metrics::start_timer(&metrics::PERSIST_FORK_CHOICE);
Expand Down
5 changes: 5 additions & 0 deletions beacon_node/lighthouse_network/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ pub struct Config {

/// Whether to enable partial data column support.
pub enable_partial_columns: bool,

/// Disable range sync and route every peer with an unknown head straight to block (lookup)
/// sync, regardless of how far ahead the peer is. Experimental.
pub disable_range_sync: bool,
}

impl Config {
Expand Down Expand Up @@ -372,6 +376,7 @@ impl Default for Config {
idontwant_message_size_threshold: DEFAULT_IDONTWANT_MESSAGE_SIZE_THRESHOLD,
advertise_false_custody_group_count: None,
enable_partial_columns: false,
disable_range_sync: false,
}
}
}
Expand Down
24 changes: 21 additions & 3 deletions beacon_node/network/src/sync/block_lookups/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ pub struct BlockLookups<T: BeaconChainTypes> {
// TODO: Why not index lookups by block_root?
single_block_lookups: FnvHashMap<SingleLookupId, SingleBlockLookup<T>>,

/// Maximum depth of the parent chain searched before forcing range sync. `PARENT_DEPTH_TOLERANCE`
/// normally, unbounded when range sync is disabled (`--disable-range-sync`).
max_parent_depth: usize,

/// Maximum number of concurrent block lookups. `MAX_LOOKUPS` normally, unbounded when range sync
/// is disabled (`--disable-range-sync`).
max_lookups: usize,

/// Used for testing assertions
metrics: BlockLookupsMetrics,
}
Expand All @@ -117,12 +125,22 @@ pub(crate) struct BlockLookupSummary {
}

impl<T: BeaconChainTypes> BlockLookups<T> {
pub fn new() -> Self {
pub fn new(disable_range_sync: bool) -> Self {
// Range sync is the fallback when a parent chain grows too long or too many lookups pile
// up. With range sync disabled there is no fallback, so the caps are removed (unbounded)
// and lookup sync follows the full tree of lookups without dropping chains.
let (max_parent_depth, max_lookups) = if disable_range_sync {
(usize::MAX, usize::MAX)
} else {
(PARENT_DEPTH_TOLERANCE, MAX_LOOKUPS)
};
Self {
ignored_chains: LRUTimeCache::new(Duration::from_secs(
IGNORED_CHAINS_CACHE_EXPIRY_SECONDS,
)),
single_block_lookups: Default::default(),
max_parent_depth,
max_lookups,
metrics: <_>::default(),
}
}
Expand Down Expand Up @@ -247,7 +265,7 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
let trigger_is_chain_tip = parent_chain.tip == child_block_root_trigger;

if (block_would_extend_chain || trigger_is_chain_tip)
&& parent_chain.len() >= PARENT_DEPTH_TOLERANCE
&& parent_chain.len() >= self.max_parent_depth
{
debug!(block_root = ?block_root_to_search, "Parent lookup chain too long");

Expand Down Expand Up @@ -379,7 +397,7 @@ impl<T: BeaconChainTypes> BlockLookups<T> {

// Lookups contain untrusted data, bound the total count of lookups hold in memory to reduce
// the risk of OOM in case of bugs of malicious activity.
if self.single_block_lookups.len() >= MAX_LOOKUPS {
if self.single_block_lookups.len() >= self.max_lookups {
warn!(?block_root, "Dropping lookup reached max");
return false;
}
Expand Down
Loading
Loading