reth_chain_state/
chain_info.rs1use 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
37 Self {
38 inner: Arc::new(ChainInfoInner {
39 last_forkchoice_update: RwLock::new(None),
40
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 get_canonical_head(&self) -> SealedHeader<N::BlockHeader> {
67 self.inner.canonical_head.read().clone()
68 }
69
70 pub fn get_safe_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
72 self.inner.safe_block.borrow().clone()
73 }
74
75 pub fn get_finalized_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
77 self.inner.finalized_block.borrow().clone()
78 }
79
80 pub fn get_canonical_num_hash(&self) -> BlockNumHash {
82 self.inner.canonical_head.read().num_hash()
83 }
84
85 pub fn get_canonical_block_number(&self) -> BlockNumber {
87 self.inner.canonical_head_number.load(Ordering::Relaxed)
88 }
89
90 pub fn get_safe_num_hash(&self) -> Option<BlockNumHash> {
92 self.inner.safe_block.borrow().as_ref().map(SealedHeader::num_hash)
93 }
94
95 pub fn get_finalized_num_hash(&self) -> Option<BlockNumHash> {
97 self.inner.finalized_block.borrow().as_ref().map(SealedHeader::num_hash)
98 }
99
100 pub fn set_canonical_head(&self, header: SealedHeader<N::BlockHeader>) {
102 let number = header.number();
103 *self.inner.canonical_head.write() = header;
104
105 self.inner.canonical_head_number.store(number, Ordering::Relaxed);
107 }
108
109 pub fn set_safe(&self, header: SealedHeader<N::BlockHeader>) {
111 self.inner.safe_block.send_if_modified(|current_header| {
112 if current_header.as_ref().map(SealedHeader::hash) != Some(header.hash()) {
113 let _ = current_header.replace(header);
114 return true
115 }
116
117 false
118 });
119 }
120
121 pub fn set_finalized(&self, header: SealedHeader<N::BlockHeader>) {
123 self.inner.finalized_block.send_if_modified(|current_header| {
124 if current_header.as_ref().map(SealedHeader::hash) != Some(header.hash()) {
125 let _ = current_header.replace(header);
126 return true
127 }
128
129 false
130 });
131 }
132
133 pub fn subscribe_finalized_block(
135 &self,
136 ) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
137 self.inner.finalized_block.subscribe()
138 }
139
140 pub fn subscribe_safe_block(&self) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
142 self.inner.safe_block.subscribe()
143 }
144}
145
146#[derive(Debug)]
148struct ChainInfoInner<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
149 last_forkchoice_update: RwLock<Option<Instant>>,
153
154 canonical_head_number: AtomicU64,
156 canonical_head: RwLock<SealedHeader<N::BlockHeader>>,
158 safe_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
160 finalized_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use reth_ethereum_primitives::EthPrimitives;
168 use reth_testing_utils::{generators, generators::random_header};
169
170 #[test]
171 fn test_chain_info() {
172 let mut rng = generators::rng();
174 let header = random_header(&mut rng, 10, None);
175
176 let tracker: ChainInfoTracker<EthPrimitives> =
178 ChainInfoTracker::new(header.clone(), None, None);
179
180 let chain_info = tracker.chain_info();
182
183 assert_eq!(chain_info.best_number, header.number);
185 assert_eq!(chain_info.best_hash, header.hash());
186 }
187
188 #[test]
189 fn test_on_forkchoice_update_received() {
190 let mut rng = generators::rng();
192 let header = random_header(&mut rng, 10, None);
193
194 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header, None, None);
196
197 assert!(tracker.last_forkchoice_update_received_at().is_none());
199
200 tracker.on_forkchoice_update_received();
202
203 assert!(tracker.last_forkchoice_update_received_at().is_some());
205 }
206
207 #[test]
208 fn test_set_canonical_head() {
209 let mut rng = generators::rng();
211 let header1 = random_header(&mut rng, 10, None);
213 let header2 = random_header(&mut rng, 20, None);
214
215 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
217
218 tracker.set_canonical_head(header2.clone());
220
221 let canonical_head = tracker.get_canonical_head();
223 assert_eq!(canonical_head, header2);
224 }
225
226 #[test]
227 fn test_set_safe() {
228 let mut rng = generators::rng();
230
231 let header1 = random_header(&mut rng, 10, None);
234 let header2 = random_header(&mut rng, 20, None);
235
236 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
238
239 tracker.set_safe(header2.clone());
241
242 let safe_header = tracker.get_safe_header();
244 assert!(safe_header.is_some()); let safe_header = safe_header.unwrap();
246 assert_eq!(safe_header, header2);
247
248 tracker.set_safe(header2.clone());
251
252 let same_safe_header = tracker.get_safe_header();
254 assert!(same_safe_header.is_some());
255 let same_safe_header = same_safe_header.unwrap();
256 assert_eq!(same_safe_header, header2);
257
258 let header3 = random_header(&mut rng, 30, None);
261
262 tracker.set_safe(header3.clone());
264
265 let updated_safe_header = tracker.get_safe_header();
267 assert!(updated_safe_header.is_some());
268 let updated_safe_header = updated_safe_header.unwrap();
269 assert_eq!(updated_safe_header, header3);
270 }
271
272 #[test]
273 fn test_set_finalized() {
274 let mut rng = generators::rng();
276
277 let header1 = random_header(&mut rng, 10, None);
279 let header2 = random_header(&mut rng, 20, None);
280 let header3 = random_header(&mut rng, 30, None);
281
282 let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
284
285 assert!(tracker.get_finalized_header().is_none());
287
288 tracker.set_finalized(header2.clone());
290
291 let finalized_header = tracker.get_finalized_header();
293 assert!(finalized_header.is_some());
294 let finalized_header = finalized_header.unwrap();
295 assert_eq!(finalized_header, header2);
296
297 tracker.set_finalized(header2.clone());
299
300 let unchanged_finalized_header = tracker.get_finalized_header();
302 assert_eq!(unchanged_finalized_header.unwrap(), header2); tracker.set_finalized(header3.clone());
306
307 let updated_finalized_header = tracker.get_finalized_header();
309 assert!(updated_finalized_header.is_some());
310 assert_eq!(updated_finalized_header.unwrap(), header3);
311 }
312
313 #[test]
314 fn test_get_finalized_num_hash() {
315 let mut rng = generators::rng();
317 let finalized_header = random_header(&mut rng, 10, None);
318
319 let tracker: ChainInfoTracker<EthPrimitives> =
321 ChainInfoTracker::new(finalized_header.clone(), Some(finalized_header.clone()), None);
322
323 assert_eq!(tracker.get_finalized_num_hash(), Some(finalized_header.num_hash()));
325 }
326
327 #[test]
328 fn test_get_safe_num_hash() {
329 let mut rng = generators::rng();
331 let safe_header = random_header(&mut rng, 10, None);
332
333 let tracker: ChainInfoTracker<EthPrimitives> =
335 ChainInfoTracker::new(safe_header.clone(), None, None);
336 tracker.set_safe(safe_header.clone());
337
338 assert_eq!(tracker.get_safe_num_hash(), Some(safe_header.num_hash()));
340 }
341}