mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-31 05:07:12 +00:00
feat(focil): payload envelope event trigger for IL service
Add a payload envelope monitor that subscribes to the ExecutionPayloadAvailable SSE event (following the existing beacon_head_monitor pattern). The inclusion list service now races the IL deadline (66.67% into slot) against the payload envelope event, matching Lodestar's Promise.race approach. This ensures IL production fires as soon as the envelope is imported (when the EL has fresh state) rather than at a fixed offset that may be too early or too late.
This commit is contained in:
@@ -4,8 +4,12 @@
|
||||
|
||||
pub mod beacon_head_monitor;
|
||||
pub mod beacon_node_health;
|
||||
pub mod payload_envelope_monitor;
|
||||
|
||||
use beacon_head_monitor::{BeaconHeadCache, HeadEvent, poll_head_event_from_beacon_nodes};
|
||||
use payload_envelope_monitor::{
|
||||
PayloadEnvelopeEvent, poll_payload_envelope_event_from_beacon_nodes,
|
||||
};
|
||||
use beacon_node_health::{
|
||||
BeaconNodeHealth, BeaconNodeSyncDistanceTiers, ExecutionEngineHealth, IsOptimistic,
|
||||
SyncDistanceTier, check_node_health,
|
||||
@@ -99,6 +103,35 @@ pub fn start_fallback_updater_service<T: SlotClock + 'static, E: EthSpec>(
|
||||
executor.spawn(head_monitor_future, "head_monitoring");
|
||||
}
|
||||
|
||||
// Start the payload envelope monitoring service if configured.
|
||||
let beacon_nodes_ref2 = beacon_nodes.clone();
|
||||
if beacon_nodes_ref2.payload_envelope_send.is_some() {
|
||||
let payload_envelope_future = async move {
|
||||
loop {
|
||||
if let Err(error) =
|
||||
poll_payload_envelope_event_from_beacon_nodes::<E, T>(
|
||||
beacon_nodes_ref2.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
error,
|
||||
"Payload envelope service failed, retrying next slot"
|
||||
);
|
||||
|
||||
let sleep_time = beacon_nodes_ref2
|
||||
.slot_clock
|
||||
.as_ref()
|
||||
.and_then(|slot_clock| slot_clock.duration_to_next_slot())
|
||||
.unwrap_or_else(|| beacon_nodes_ref2.spec.get_slot_duration());
|
||||
sleep(sleep_time).await
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
executor.spawn(payload_envelope_future, "payload_envelope_monitoring");
|
||||
}
|
||||
|
||||
let future = async move {
|
||||
loop {
|
||||
beacon_nodes.update_all_candidates::<E>().await;
|
||||
@@ -423,6 +456,7 @@ pub struct BeaconNodeFallback<T> {
|
||||
slot_clock: Option<T>,
|
||||
beacon_head_cache: Option<Arc<BeaconHeadCache>>,
|
||||
head_monitor_send: Option<Arc<mpsc::Sender<HeadEvent>>>,
|
||||
pub payload_envelope_send: Option<Arc<mpsc::Sender<PayloadEnvelopeEvent>>>,
|
||||
broadcast_topics: Vec<ApiTopic>,
|
||||
spec: Arc<ChainSpec>,
|
||||
}
|
||||
@@ -441,6 +475,7 @@ impl<T: SlotClock> BeaconNodeFallback<T> {
|
||||
slot_clock: None,
|
||||
beacon_head_cache: None,
|
||||
head_monitor_send: None,
|
||||
payload_envelope_send: None,
|
||||
broadcast_topics,
|
||||
spec,
|
||||
}
|
||||
@@ -464,6 +499,15 @@ impl<T: SlotClock> BeaconNodeFallback<T> {
|
||||
self.beacon_head_cache = Some(Arc::new(BeaconHeadCache::new()));
|
||||
}
|
||||
|
||||
/// Sets the payload envelope monitor channel that streams `ExecutionPayloadAvailable`
|
||||
/// events from all the beacon nodes that the validator client is connected to.
|
||||
pub fn set_payload_envelope_send(
|
||||
&mut self,
|
||||
payload_envelope_send: Arc<mpsc::Sender<PayloadEnvelopeEvent>>,
|
||||
) {
|
||||
self.payload_envelope_send = Some(payload_envelope_send);
|
||||
}
|
||||
|
||||
/// The count of candidates, regardless of their state.
|
||||
pub async fn num_total(&self) -> usize {
|
||||
self.candidates.read().await.len()
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use crate::BeaconNodeFallback;
|
||||
use eth2::types::{EventKind, EventTopic, Hash256};
|
||||
use futures::StreamExt;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, info, warn};
|
||||
use types::{EthSpec, Slot};
|
||||
|
||||
/// Event emitted when an execution payload envelope becomes available for a slot.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PayloadEnvelopeEvent {
|
||||
pub slot: Slot,
|
||||
pub block_root: Hash256,
|
||||
}
|
||||
|
||||
/// Runs a non-terminating loop that subscribes to `ExecutionPayloadAvailable` SSE events
|
||||
/// from all connected beacon nodes and forwards them over an mpsc channel.
|
||||
///
|
||||
/// This follows the same pattern as `poll_head_event_from_beacon_nodes`.
|
||||
pub async fn poll_payload_envelope_event_from_beacon_nodes<E: EthSpec, T: SlotClock + 'static>(
|
||||
beacon_nodes: Arc<BeaconNodeFallback<T>>,
|
||||
) -> Result<(), String> {
|
||||
let payload_envelope_send = beacon_nodes
|
||||
.payload_envelope_send
|
||||
.clone()
|
||||
.ok_or("Unable to start payload envelope monitor without payload_envelope_send")?;
|
||||
|
||||
info!("Starting payload envelope monitoring service");
|
||||
let candidates = {
|
||||
let candidates_guard = beacon_nodes.candidates.read().await;
|
||||
candidates_guard.clone()
|
||||
};
|
||||
|
||||
// Create Vec of streams, which we will select over.
|
||||
let mut streams = vec![];
|
||||
|
||||
for candidate in &candidates {
|
||||
let event_stream = candidate
|
||||
.beacon_node
|
||||
.get_events::<E>(&[EventTopic::ExecutionPayloadAvailable])
|
||||
.await;
|
||||
|
||||
let event_stream = match event_stream {
|
||||
Ok(stream) => stream,
|
||||
Err(e) => {
|
||||
warn!(error = ?e, node_index = candidate.index, "Failed to get execution payload available event stream");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
streams.push(event_stream.map(|event| (candidate.index, event)));
|
||||
}
|
||||
|
||||
if streams.is_empty() {
|
||||
return Err(
|
||||
"No beacon nodes available for execution payload available event streaming".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Combine streams into a single stream and poll events from any of them.
|
||||
let mut combined_stream = futures::stream::select_all(streams);
|
||||
|
||||
while let Some((candidate_index, event_result)) = combined_stream.next().await {
|
||||
match event_result {
|
||||
Ok(EventKind::ExecutionPayloadAvailable(payload_event)) => {
|
||||
debug!(
|
||||
candidate_index,
|
||||
block_root = ?payload_event.block_root,
|
||||
slot = %payload_event.slot,
|
||||
"Execution payload available from beacon node"
|
||||
);
|
||||
|
||||
if payload_envelope_send
|
||||
.send(PayloadEnvelopeEvent {
|
||||
slot: payload_event.slot,
|
||||
block_root: payload_event.block_root,
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return Err("Payload envelope monitoring service channel closed".into());
|
||||
}
|
||||
}
|
||||
Ok(event) => {
|
||||
warn!(
|
||||
event_kind = event.topic_name(),
|
||||
candidate_index, "Received unexpected event from BN in payload envelope monitor"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"Payload envelope monitoring stream error, node: {candidate_index}, error: {e:?}"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("Payload envelope stream ended unexpectedly".into())
|
||||
}
|
||||
Reference in New Issue
Block a user