1use alloy_consensus::BlockHeader;
2use alloy_eips::BlockNumHash;
3use alloy_primitives::BlockNumber;
4use parking_lot::RwLock;
5use reth_chainspec::ChainInfo;
6use reth_primitives::{NodePrimitives, SealedHeader};
7use std::{
8 sync::{
9 atomic::{AtomicU64, Ordering},
10 Arc,
11 },
12 time::Instant,
13};
14use tokio::sync::watch;
15
16#[derive(Debug, Clone)]
18pub struct ChainInfoTracker<N: NodePrimitives> {
19 inner: Arc<ChainInfoInner<N>>,
20}
21
22impl<N> ChainInfoTracker<N>
23where
24 N: NodePrimitives,
25 N::BlockHeader: BlockHeader,
26{
27 pub fn new(
30 head: SealedHeader<N::BlockHeader>,
31 finalized: Option<SealedHeader<N::BlockHeader>>,
32 safe: Option<SealedHeader<N::BlockHeader>>,
33 ) -> Self {
34 let (finalized_block, _) = watch::channel(finalized);
35 let (safe_block, _) = watch::channel(safe);
36
37 Self {
38 inner: Arc::new(ChainInfoInner {
39 last_forkchoice_update: RwLock::new(None),
40 last_transition_configuration_exchange: RwLock::new(None),
41 canonical_head_number: AtomicU64::new(head.number()),
42 canonical_head: RwLock::new(head),
43 safe_block,
44 finalized_block,
45 }),
46 }
47 }
48
49 pub fn chain_info(&self) -> ChainInfo {
51 let inner = self.inner.canonical_head.read();
52 ChainInfo { best_hash: inner.hash(), best_number: inner.number() }
53 }
54
55 pub fn on_forkchoice_update_received(&self) {
57 self.inner.last_forkchoice_update.write().replace(Instant::now());
58 }
59
60 pub fn last_forkchoice_update_received_at(&self) -> Option<Instant> {
62 *self.inner.last_forkchoice_update.read()
63 }
64
65 pub fn on_transition_configuration_exchanged(&self) {
67 self.inner.last_transition_configuration_exchange.write().replace(Instant::now());
68 }
69
70 pub fn last_transition_configuration_exchanged_at(&self) -> Option<Instant> {
72 *self.inner.last_transition_configuration_exchange.read()
73 }
74
75 pub fn get_canonical_head(&self) -> SealedHeader<N::BlockHeader> {
77 self.inner.canonical_head.read().clone()
78 }
79
80 pub fn get_safe_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
82 self.inner.safe_block.borrow().clone()
83 }
84
85 pub fn get_finalized_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
87 self.inner.finalized_block.borrow().clone()
88 }
89
90 #[allow(dead_code)]
92 pub fn get_canonical_num_hash(&self) -> BlockNumHash {
93 self.inner.canonical_head.read().num_hash()
94 }
95
96 pub fn get_canonical_block_number(&self) -> BlockNumber {
98 self.inner.canonical_head_number.load(Ordering::Relaxed)
99 }
100
101 pub fn get_safe_num_hash(&self) -> Option<BlockNumHash> {
103 self.inner.safe_block.borrow().as_ref().map(SealedHeader::num_hash)
104 }
105
106 pub fn get_finalized_num_hash(&self) -> Option<BlockNumHash> {
108 self.inner.finalized_block.borrow().as_ref().map(SealedHeader::num_hash)
109 }
110
111 pub fn set_canonical_head(&self, header: SealedHeader<N::BlockHeader>) {
113 let number = header.number();
114 *self.inner.canonical_head.write() = header;
115
116 self.inner.canonical_head_number.store(number, Ordering::Relaxed);
118 }
119
120 pub fn set_safe(&self, header: SealedHeader<N::BlockHeader>) {
122 self.inner.safe_block.send_if_modified(|current_header| {
123 if current_header.as_ref().map(SealedHeader::hash) != Some(header.hash()) {
124 let _ = current_header.replace(header);
125 return true
126 }
127
128 false
129 });
130 }
131
132 pub fn set_finalized(&self, header: SealedHeader<N::BlockHeader>) {
134 self.inner.finalized_block.send_if_modified(|current_header| {
135 if current_header.as_ref().map(SealedHeader::hash) != Some(header.hash()) {
136 let _ = current_header.replace(header);
137 return true
138 }
139
140 false
141 });
142 }
143
144 pub fn subscribe_finalized_block(
146 &self,
147 ) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
148 self.inner.finalized_block.subscribe()
149 }
150
151 pub fn subscribe_safe_block(&self) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
153 self.inner.safe_block.subscribe()
154 }
155}
156
157#[derive(Debug)]
159struct ChainInfoInner<N: NodePrimitives = reth_primitives::EthPrimitives> {
160 last_forkchoice_update: RwLock<Option<Instant>>,
164 last_transition_configuration_exchange: RwLock<Option<Instant>>,
168 canonical_head_number: AtomicU64,
170 canonical_head: RwLock<SealedHeader<N::BlockHeader>>,
172 safe_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
174 finalized_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use reth_primitives::EthPrimitives;
182 use reth_testing_utils::{generators, generators::random_header};
183
184 #[test]
185 fn test_chain_info() {
186 let mut rng = generators::rng();
188 let header = random_header(&mut rng, 10, None);
189
190 let tracker: ChainInfoTracker<EthPrimitives> =
192 ChainInfoTracker::new(header.clone(), None, None);
193
194 let chain_info = tracker.chain_info();
196
197 assert_eq!(chain_info.best_number, header.number);
199 assert_eq!(chain_info.best_hash, header.hash());
200 }
201
202 #[test]
203 fn test_on_forkchoice_update_received() {
204 let mut rng = generators::rng();
206 let header = random_header(&mut rng, 10, None);
207
208 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header, None, None);
210
211 assert!(tracker.last_forkchoice_update_received_at().is_none());
213
214 tracker.on_forkchoice_update_received();
216
217 assert!(tracker.last_forkchoice_update_received_at().is_some());
219 }
220
221 #[test]
222 fn test_on_transition_configuration_exchanged() {
223 let mut rng = generators::rng();
225 let header = random_header(&mut rng, 10, None);
226
227 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header, None, None);
229
230 assert!(tracker.last_transition_configuration_exchanged_at().is_none());
233
234 tracker.on_transition_configuration_exchanged();
236
237 assert!(tracker.last_transition_configuration_exchanged_at().is_some());
240 }
241
242 #[test]
243 fn test_set_canonical_head() {
244 let mut rng = generators::rng();
246 let header1 = random_header(&mut rng, 10, None);
248 let header2 = random_header(&mut rng, 20, None);
249
250 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
252
253 tracker.set_canonical_head(header2.clone());
255
256 let canonical_head = tracker.get_canonical_head();
258 assert_eq!(canonical_head, header2);
259 }
260
261 #[test]
262 fn test_set_safe() {
263 let mut rng = generators::rng();
265
266 let header1 = random_header(&mut rng, 10, None);
269 let header2 = random_header(&mut rng, 20, None);
270
271 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
273
274 tracker.set_safe(header2.clone());
276
277 let safe_header = tracker.get_safe_header();
279 assert!(safe_header.is_some()); let safe_header = safe_header.unwrap();
281 assert_eq!(safe_header, header2);
282
283 tracker.set_safe(header2.clone());
286
287 let same_safe_header = tracker.get_safe_header();
289 assert!(same_safe_header.is_some());
290 let same_safe_header = same_safe_header.unwrap();
291 assert_eq!(same_safe_header, header2);
292
293 let header3 = random_header(&mut rng, 30, None);
296
297 tracker.set_safe(header3.clone());
299
300 let updated_safe_header = tracker.get_safe_header();
302 assert!(updated_safe_header.is_some());
303 let updated_safe_header = updated_safe_header.unwrap();
304 assert_eq!(updated_safe_header, header3);
305 }
306
307 #[test]
308 fn test_set_finalized() {
309 let mut rng = generators::rng();
311
312 let header1 = random_header(&mut rng, 10, None);
314 let header2 = random_header(&mut rng, 20, None);
315 let header3 = random_header(&mut rng, 30, None);
316
317 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
319
320 assert!(tracker.get_finalized_header().is_none());
322
323 tracker.set_finalized(header2.clone());
325
326 let finalized_header = tracker.get_finalized_header();
328 assert!(finalized_header.is_some());
329 let finalized_header = finalized_header.unwrap();
330 assert_eq!(finalized_header, header2);
331
332 tracker.set_finalized(header2.clone());
334
335 let unchanged_finalized_header = tracker.get_finalized_header();
337 assert_eq!(unchanged_finalized_header.unwrap(), header2); tracker.set_finalized(header3.clone());
341
342 let updated_finalized_header = tracker.get_finalized_header();
344 assert!(updated_finalized_header.is_some());
345 assert_eq!(updated_finalized_header.unwrap(), header3);
346 }
347
348 #[test]
349 fn test_get_finalized_num_hash() {
350 let mut rng = generators::rng();
352 let finalized_header = random_header(&mut rng, 10, None);
353
354 let tracker: ChainInfoTracker<EthPrimitives> =
356 ChainInfoTracker::new(finalized_header.clone(), Some(finalized_header.clone()), None);
357
358 assert_eq!(tracker.get_finalized_num_hash(), Some(finalized_header.num_hash()));
360 }
361
362 #[test]
363 fn test_get_safe_num_hash() {
364 let mut rng = generators::rng();
366 let safe_header = random_header(&mut rng, 10, None);
367
368 let tracker: ChainInfoTracker<EthPrimitives> =
370 ChainInfoTracker::new(safe_header.clone(), None, None);
371 tracker.set_safe(safe_header.clone());
372
373 assert_eq!(tracker.get_safe_num_hash(), Some(safe_header.num_hash()));
375 }
376}