kona_engine/state/
core.rs1use crate::Metrics;
4use alloy_rpc_types_engine::ForkchoiceState;
5use kona_protocol::L2BlockInfo;
6
7#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
9pub struct EngineSyncState {
10 unsafe_head: L2BlockInfo,
12 cross_unsafe_head: L2BlockInfo,
14 local_safe_head: L2BlockInfo,
17 safe_head: L2BlockInfo,
19 finalized_head: L2BlockInfo,
22}
23
24impl EngineSyncState {
25 pub const fn unsafe_head(&self) -> L2BlockInfo {
27 self.unsafe_head
28 }
29
30 pub const fn cross_unsafe_head(&self) -> L2BlockInfo {
32 self.cross_unsafe_head
33 }
34
35 pub const fn local_safe_head(&self) -> L2BlockInfo {
37 self.local_safe_head
38 }
39
40 pub const fn safe_head(&self) -> L2BlockInfo {
42 self.safe_head
43 }
44
45 pub const fn finalized_head(&self) -> L2BlockInfo {
47 self.finalized_head
48 }
49
50 pub const fn create_forkchoice_state(&self) -> ForkchoiceState {
58 ForkchoiceState {
59 head_block_hash: self.unsafe_head.hash(),
60 safe_block_hash: self.safe_head.hash(),
61 finalized_block_hash: self.finalized_head.hash(),
62 }
63 }
64
65 pub fn apply_update(self, sync_state_update: EngineSyncStateUpdate) -> Self {
68 if let Some(unsafe_head) = sync_state_update.unsafe_head {
69 Self::update_block_label_metric(
70 Metrics::UNSAFE_BLOCK_LABEL,
71 unsafe_head.block_info.number,
72 );
73 }
74 if let Some(cross_unsafe_head) = sync_state_update.cross_unsafe_head {
75 Self::update_block_label_metric(
76 Metrics::CROSS_UNSAFE_BLOCK_LABEL,
77 cross_unsafe_head.block_info.number,
78 );
79 }
80 if let Some(local_safe_head) = sync_state_update.local_safe_head {
81 Self::update_block_label_metric(
82 Metrics::LOCAL_SAFE_BLOCK_LABEL,
83 local_safe_head.block_info.number,
84 );
85 }
86 if let Some(safe_head) = sync_state_update.safe_head {
87 Self::update_block_label_metric(Metrics::SAFE_BLOCK_LABEL, safe_head.block_info.number);
88 }
89 if let Some(finalized_head) = sync_state_update.finalized_head {
90 Self::update_block_label_metric(
91 Metrics::FINALIZED_BLOCK_LABEL,
92 finalized_head.block_info.number,
93 );
94 }
95
96 Self {
97 unsafe_head: sync_state_update.unsafe_head.unwrap_or(self.unsafe_head),
98 cross_unsafe_head: sync_state_update
99 .cross_unsafe_head
100 .unwrap_or(self.cross_unsafe_head),
101 local_safe_head: sync_state_update.local_safe_head.unwrap_or(self.local_safe_head),
102 safe_head: sync_state_update.safe_head.unwrap_or(self.safe_head),
103 finalized_head: sync_state_update.finalized_head.unwrap_or(self.finalized_head),
104 }
105 }
106
107 #[inline]
109 fn update_block_label_metric(label: &'static str, number: u64) {
110 kona_macros::set!(gauge, Metrics::BLOCK_LABELS, "label", label, number as f64);
111 }
112}
113
114#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
116pub struct EngineSyncStateUpdate {
117 pub unsafe_head: Option<L2BlockInfo>,
119 pub cross_unsafe_head: Option<L2BlockInfo>,
121 pub local_safe_head: Option<L2BlockInfo>,
124 pub safe_head: Option<L2BlockInfo>,
126 pub finalized_head: Option<L2BlockInfo>,
129}
130
131#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
133pub struct EngineState {
134 pub sync_state: EngineSyncState,
136
137 pub el_sync_finished: bool,
139
140 pub need_fcu_call_backup_unsafe_reorg: bool,
146}
147
148impl EngineState {
149 pub fn needs_consolidation(&self) -> bool {
157 self.sync_state.safe_head() != self.sync_state.unsafe_head()
158 }
159}
160
161#[cfg(test)]
162mod test {
163 use super::*;
164 use crate::Metrics;
165 use kona_protocol::BlockInfo;
166 use metrics_exporter_prometheus::PrometheusBuilder;
167 use rstest::rstest;
168
169 impl EngineState {
170 pub fn set_unsafe_head(&mut self, unsafe_head: L2BlockInfo) {
172 self.sync_state.apply_update(EngineSyncStateUpdate {
173 unsafe_head: Some(unsafe_head),
174 ..Default::default()
175 });
176 }
177
178 pub fn set_cross_unsafe_head(&mut self, cross_unsafe_head: L2BlockInfo) {
180 self.sync_state.apply_update(EngineSyncStateUpdate {
181 cross_unsafe_head: Some(cross_unsafe_head),
182 ..Default::default()
183 });
184 }
185
186 pub fn set_local_safe_head(&mut self, local_safe_head: L2BlockInfo) {
188 self.sync_state.apply_update(EngineSyncStateUpdate {
189 local_safe_head: Some(local_safe_head),
190 ..Default::default()
191 });
192 }
193
194 pub fn set_safe_head(&mut self, safe_head: L2BlockInfo) {
196 self.sync_state.apply_update(EngineSyncStateUpdate {
197 safe_head: Some(safe_head),
198 ..Default::default()
199 });
200 }
201
202 pub fn set_finalized_head(&mut self, finalized_head: L2BlockInfo) {
204 self.sync_state.apply_update(EngineSyncStateUpdate {
205 finalized_head: Some(finalized_head),
206 ..Default::default()
207 });
208 }
209 }
210
211 #[rstest]
212 #[case::set_unsafe(EngineState::set_unsafe_head, Metrics::UNSAFE_BLOCK_LABEL, 1)]
213 #[case::set_cross_unsafe(
214 EngineState::set_cross_unsafe_head,
215 Metrics::CROSS_UNSAFE_BLOCK_LABEL,
216 2
217 )]
218 #[case::set_local_safe(EngineState::set_local_safe_head, Metrics::LOCAL_SAFE_BLOCK_LABEL, 3)]
219 #[case::set_safe_head(EngineState::set_safe_head, Metrics::SAFE_BLOCK_LABEL, 4)]
220 #[case::set_finalized_head(EngineState::set_finalized_head, Metrics::FINALIZED_BLOCK_LABEL, 5)]
221 #[cfg(feature = "metrics")]
222 fn test_chain_label_metrics(
223 #[case] set_fn: impl Fn(&mut EngineState, L2BlockInfo),
224 #[case] label_name: &str,
225 #[case] number: u64,
226 ) {
227 let handle = PrometheusBuilder::new().install_recorder().unwrap();
228 crate::Metrics::init();
229
230 let mut state = EngineState::default();
231 set_fn(
232 &mut state,
233 L2BlockInfo {
234 block_info: BlockInfo { number, ..Default::default() },
235 ..Default::default()
236 },
237 );
238
239 assert!(handle.render().contains(
240 format!("kona_node_block_labels{{label=\"{label_name}\"}} {number}").as_str()
241 ));
242 }
243}