use crate::Metrics;
use alloy_rpc_types_engine::ForkchoiceState;
use kona_protocol::L2BlockInfo;
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct EngineSyncState {
unsafe_head: L2BlockInfo,
cross_unsafe_head: L2BlockInfo,
local_safe_head: L2BlockInfo,
safe_head: L2BlockInfo,
finalized_head: L2BlockInfo,
}
impl EngineSyncState {
pub const fn unsafe_head(&self) -> L2BlockInfo {
self.unsafe_head
}
pub const fn cross_unsafe_head(&self) -> L2BlockInfo {
self.cross_unsafe_head
}
pub const fn local_safe_head(&self) -> L2BlockInfo {
self.local_safe_head
}
pub const fn safe_head(&self) -> L2BlockInfo {
self.safe_head
}
pub const fn finalized_head(&self) -> L2BlockInfo {
self.finalized_head
}
pub const fn create_forkchoice_state(&self) -> ForkchoiceState {
ForkchoiceState {
head_block_hash: self.unsafe_head.hash(),
safe_block_hash: self.safe_head.hash(),
finalized_block_hash: self.finalized_head.hash(),
}
}
pub fn apply_update(self, sync_state_update: EngineSyncStateUpdate) -> Self {
if let Some(unsafe_head) = sync_state_update.unsafe_head {
Self::update_block_label_metric(
Metrics::UNSAFE_BLOCK_LABEL,
unsafe_head.block_info.number,
);
}
if let Some(cross_unsafe_head) = sync_state_update.cross_unsafe_head {
Self::update_block_label_metric(
Metrics::CROSS_UNSAFE_BLOCK_LABEL,
cross_unsafe_head.block_info.number,
);
}
if let Some(local_safe_head) = sync_state_update.local_safe_head {
Self::update_block_label_metric(
Metrics::LOCAL_SAFE_BLOCK_LABEL,
local_safe_head.block_info.number,
);
}
if let Some(safe_head) = sync_state_update.safe_head {
Self::update_block_label_metric(Metrics::SAFE_BLOCK_LABEL, safe_head.block_info.number);
}
if let Some(finalized_head) = sync_state_update.finalized_head {
Self::update_block_label_metric(
Metrics::FINALIZED_BLOCK_LABEL,
finalized_head.block_info.number,
);
}
Self {
unsafe_head: sync_state_update.unsafe_head.unwrap_or(self.unsafe_head),
cross_unsafe_head: sync_state_update
.cross_unsafe_head
.unwrap_or(self.cross_unsafe_head),
local_safe_head: sync_state_update.local_safe_head.unwrap_or(self.local_safe_head),
safe_head: sync_state_update.safe_head.unwrap_or(self.safe_head),
finalized_head: sync_state_update.finalized_head.unwrap_or(self.finalized_head),
}
}
#[inline]
fn update_block_label_metric(label: &'static str, number: u64) {
kona_macros::set!(gauge, Metrics::BLOCK_LABELS, "label", label, number as f64);
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EngineSyncStateUpdate {
pub unsafe_head: Option<L2BlockInfo>,
pub cross_unsafe_head: Option<L2BlockInfo>,
pub local_safe_head: Option<L2BlockInfo>,
pub safe_head: Option<L2BlockInfo>,
pub finalized_head: Option<L2BlockInfo>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct EngineState {
pub sync_state: EngineSyncState,
pub el_sync_finished: bool,
pub need_fcu_call_backup_unsafe_reorg: bool,
}
impl EngineState {
pub fn needs_consolidation(&self) -> bool {
self.sync_state.safe_head() != self.sync_state.unsafe_head()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Metrics;
use kona_protocol::BlockInfo;
use metrics_exporter_prometheus::PrometheusBuilder;
use rstest::rstest;
impl EngineState {
pub fn set_unsafe_head(&mut self, unsafe_head: L2BlockInfo) {
self.sync_state.apply_update(EngineSyncStateUpdate {
unsafe_head: Some(unsafe_head),
..Default::default()
});
}
pub fn set_cross_unsafe_head(&mut self, cross_unsafe_head: L2BlockInfo) {
self.sync_state.apply_update(EngineSyncStateUpdate {
cross_unsafe_head: Some(cross_unsafe_head),
..Default::default()
});
}
pub fn set_local_safe_head(&mut self, local_safe_head: L2BlockInfo) {
self.sync_state.apply_update(EngineSyncStateUpdate {
local_safe_head: Some(local_safe_head),
..Default::default()
});
}
pub fn set_safe_head(&mut self, safe_head: L2BlockInfo) {
self.sync_state.apply_update(EngineSyncStateUpdate {
safe_head: Some(safe_head),
..Default::default()
});
}
pub fn set_finalized_head(&mut self, finalized_head: L2BlockInfo) {
self.sync_state.apply_update(EngineSyncStateUpdate {
finalized_head: Some(finalized_head),
..Default::default()
});
}
}
#[rstest]
#[case::set_unsafe(EngineState::set_unsafe_head, Metrics::UNSAFE_BLOCK_LABEL, 1)]
#[case::set_cross_unsafe(
EngineState::set_cross_unsafe_head,
Metrics::CROSS_UNSAFE_BLOCK_LABEL,
2
)]
#[case::set_local_safe(EngineState::set_local_safe_head, Metrics::LOCAL_SAFE_BLOCK_LABEL, 3)]
#[case::set_safe_head(EngineState::set_safe_head, Metrics::SAFE_BLOCK_LABEL, 4)]
#[case::set_finalized_head(EngineState::set_finalized_head, Metrics::FINALIZED_BLOCK_LABEL, 5)]
#[cfg(feature = "metrics")]
fn test_chain_label_metrics(
#[case] set_fn: impl Fn(&mut EngineState, L2BlockInfo),
#[case] label_name: &str,
#[case] number: u64,
) {
let handle = PrometheusBuilder::new().install_recorder().unwrap();
crate::Metrics::init();
let mut state = EngineState::default();
set_fn(
&mut state,
L2BlockInfo {
block_info: BlockInfo { number, ..Default::default() },
..Default::default()
},
);
assert!(handle.render().contains(
format!("kona_node_block_labels{{label=\"{label_name}\"}} {number}").as_str()
));
}
}