1use alloy_consensus::BlockHeader;
2use alloy_eips::BlockNumHash;
3use alloy_primitives::BlockNumber;
4use parking_lot::RwLock;
5use reth_chainspec::ChainInfo;
6use reth_primitives_traits::{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 let (persisted_block, _) = watch::channel(None);
37
38 Self {
39 inner: Arc::new(ChainInfoInner {
40 last_forkchoice_update: RwLock::new(None),
41
42 canonical_head_number: AtomicU64::new(head.number()),
43 canonical_head: RwLock::new(head),
44 safe_block,
45 finalized_block,
46 persisted_block,
47 }),
48 }
49 }
50
51 pub fn chain_info(&self) -> ChainInfo {
53 let inner = self.inner.canonical_head.read();
54 ChainInfo { best_hash: inner.hash(), best_number: inner.number() }
55 }
56
57 pub fn on_forkchoice_update_received(&self) {
59 self.inner.last_forkchoice_update.write().replace(Instant::now());
60 }
61
62 pub fn last_forkchoice_update_received_at(&self) -> Option<Instant> {
64 *self.inner.last_forkchoice_update.read()
65 }
66
67 pub fn get_canonical_head(&self) -> SealedHeader<N::BlockHeader> {
69 self.inner.canonical_head.read().clone()
70 }
71
72 pub fn get_safe_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
74 self.inner.safe_block.borrow().clone()
75 }
76
77 pub fn get_finalized_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
79 self.inner.finalized_block.borrow().clone()
80 }
81
82 pub fn get_canonical_num_hash(&self) -> BlockNumHash {
84 self.inner.canonical_head.read().num_hash()
85 }
86
87 pub fn get_canonical_block_number(&self) -> BlockNumber {
89 self.inner.canonical_head_number.load(Ordering::Relaxed)
90 }
91
92 pub fn get_safe_num_hash(&self) -> Option<BlockNumHash> {
94 self.inner.safe_block.borrow().as_ref().map(SealedHeader::num_hash)
95 }
96
97 pub fn get_finalized_num_hash(&self) -> Option<BlockNumHash> {
99 self.inner.finalized_block.borrow().as_ref().map(SealedHeader::num_hash)
100 }
101
102 pub fn get_persisted_num_hash(&self) -> Option<BlockNumHash> {
104 *self.inner.persisted_block.borrow()
105 }
106
107 pub fn set_canonical_head(&self, header: SealedHeader<N::BlockHeader>) {
109 let number = header.number();
110 *self.inner.canonical_head.write() = header;
111
112 self.inner.canonical_head_number.store(number, Ordering::Relaxed);
114 }
115
116 pub fn set_safe(&self, header: SealedHeader<N::BlockHeader>) {
118 self.inner.safe_block.send_if_modified(|current_header| {
119 if current_header.as_ref().map(SealedHeader::hash) != Some(header.hash()) {
120 let _ = current_header.replace(header);
121 return true
122 }
123
124 false
125 });
126 }
127
128 pub fn set_finalized(&self, header: SealedHeader<N::BlockHeader>) {
130 self.inner.finalized_block.send_if_modified(|current_header| {
131 if current_header.as_ref().map(SealedHeader::hash) != Some(header.hash()) {
132 let _ = current_header.replace(header);
133 return true
134 }
135
136 false
137 });
138 }
139
140 pub fn set_persisted(&self, num_hash: BlockNumHash) {
142 self.inner.persisted_block.send_if_modified(|current| {
143 if current.map(|b| b.hash) != Some(num_hash.hash) {
144 let _ = current.replace(num_hash);
145 return true
146 }
147
148 false
149 });
150 }
151
152 pub fn subscribe_finalized_block(
154 &self,
155 ) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
156 self.inner.finalized_block.subscribe()
157 }
158
159 pub fn subscribe_safe_block(&self) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
161 self.inner.safe_block.subscribe()
162 }
163
164 pub fn subscribe_persisted_block(&self) -> watch::Receiver<Option<BlockNumHash>> {
166 self.inner.persisted_block.subscribe()
167 }
168}
169
170#[derive(Debug)]
172struct ChainInfoInner<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
173 last_forkchoice_update: RwLock<Option<Instant>>,
177
178 canonical_head_number: AtomicU64,
180 canonical_head: RwLock<SealedHeader<N::BlockHeader>>,
182 safe_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
184 finalized_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
186 persisted_block: watch::Sender<Option<BlockNumHash>>,
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use alloy_primitives::B256;
194 use reth_ethereum_primitives::EthPrimitives;
195 use reth_testing_utils::{generators, generators::random_header};
196
197 #[test]
198 fn test_chain_info() {
199 let mut rng = generators::rng();
201 let header = random_header(&mut rng, 10, None);
202
203 let tracker: ChainInfoTracker<EthPrimitives> =
205 ChainInfoTracker::new(header.clone(), None, None);
206
207 let chain_info = tracker.chain_info();
209
210 assert_eq!(chain_info.best_number, header.number);
212 assert_eq!(chain_info.best_hash, header.hash());
213 }
214
215 #[test]
216 fn test_on_forkchoice_update_received() {
217 let mut rng = generators::rng();
219 let header = random_header(&mut rng, 10, None);
220
221 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header, None, None);
223
224 assert!(tracker.last_forkchoice_update_received_at().is_none());
226
227 tracker.on_forkchoice_update_received();
229
230 assert!(tracker.last_forkchoice_update_received_at().is_some());
232 }
233
234 #[test]
235 fn test_set_canonical_head() {
236 let mut rng = generators::rng();
238 let header1 = random_header(&mut rng, 10, None);
240 let header2 = random_header(&mut rng, 20, None);
241
242 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
244
245 tracker.set_canonical_head(header2.clone());
247
248 let canonical_head = tracker.get_canonical_head();
250 assert_eq!(canonical_head, header2);
251 }
252
253 #[test]
254 fn test_set_safe() {
255 let mut rng = generators::rng();
257
258 let header1 = random_header(&mut rng, 10, None);
261 let header2 = random_header(&mut rng, 20, None);
262
263 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
265
266 tracker.set_safe(header2.clone());
268
269 let safe_header = tracker.get_safe_header();
271 assert!(safe_header.is_some()); let safe_header = safe_header.unwrap();
273 assert_eq!(safe_header, header2);
274
275 tracker.set_safe(header2.clone());
278
279 let same_safe_header = tracker.get_safe_header();
281 assert!(same_safe_header.is_some());
282 let same_safe_header = same_safe_header.unwrap();
283 assert_eq!(same_safe_header, header2);
284
285 let header3 = random_header(&mut rng, 30, None);
288
289 tracker.set_safe(header3.clone());
291
292 let updated_safe_header = tracker.get_safe_header();
294 assert!(updated_safe_header.is_some());
295 let updated_safe_header = updated_safe_header.unwrap();
296 assert_eq!(updated_safe_header, header3);
297 }
298
299 #[test]
300 fn test_set_finalized() {
301 let mut rng = generators::rng();
303
304 let header1 = random_header(&mut rng, 10, None);
306 let header2 = random_header(&mut rng, 20, None);
307 let header3 = random_header(&mut rng, 30, None);
308
309 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
311
312 assert!(tracker.get_finalized_header().is_none());
314
315 tracker.set_finalized(header2.clone());
317
318 let finalized_header = tracker.get_finalized_header();
320 assert!(finalized_header.is_some());
321 let finalized_header = finalized_header.unwrap();
322 assert_eq!(finalized_header, header2);
323
324 tracker.set_finalized(header2.clone());
326
327 let unchanged_finalized_header = tracker.get_finalized_header();
329 assert_eq!(unchanged_finalized_header.unwrap(), header2); tracker.set_finalized(header3.clone());
333
334 let updated_finalized_header = tracker.get_finalized_header();
336 assert!(updated_finalized_header.is_some());
337 assert_eq!(updated_finalized_header.unwrap(), header3);
338 }
339
340 #[test]
341 fn test_get_finalized_num_hash() {
342 let mut rng = generators::rng();
344 let finalized_header = random_header(&mut rng, 10, None);
345
346 let tracker: ChainInfoTracker<EthPrimitives> =
348 ChainInfoTracker::new(finalized_header.clone(), Some(finalized_header.clone()), None);
349
350 assert_eq!(tracker.get_finalized_num_hash(), Some(finalized_header.num_hash()));
352 }
353
354 #[test]
355 fn test_get_safe_num_hash() {
356 let mut rng = generators::rng();
358 let safe_header = random_header(&mut rng, 10, None);
359
360 let tracker: ChainInfoTracker<EthPrimitives> =
362 ChainInfoTracker::new(safe_header.clone(), None, None);
363 tracker.set_safe(safe_header.clone());
364
365 assert_eq!(tracker.get_safe_num_hash(), Some(safe_header.num_hash()));
367 }
368
369 #[test]
370 fn test_set_persisted() {
371 let mut rng = generators::rng();
372 let header = random_header(&mut rng, 10, None);
373 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header, None, None);
374
375 assert!(tracker.get_persisted_num_hash().is_none());
377
378 let num_hash1 = BlockNumHash::new(10, B256::random());
380 tracker.set_persisted(num_hash1);
381 assert_eq!(tracker.get_persisted_num_hash(), Some(num_hash1));
382
383 tracker.set_persisted(num_hash1);
385 assert_eq!(tracker.get_persisted_num_hash(), Some(num_hash1));
386
387 let num_hash2 = BlockNumHash::new(20, B256::random());
389 tracker.set_persisted(num_hash2);
390 assert_eq!(tracker.get_persisted_num_hash(), Some(num_hash2));
391 }
392}