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},
10Arc,
11 },
12time::Instant,
13};
14use tokio::sync::watch;
1516/// Tracks the chain info: canonical head, safe block, finalized block.
17#[derive(Debug, Clone)]
18pub struct ChainInfoTracker<N: NodePrimitives> {
19 inner: Arc<ChainInfoInner<N>>,
20}
2122impl<N> ChainInfoTracker<N>
23where
24N: NodePrimitives,
25 N::BlockHeader: BlockHeader,
26{
27/// Create a new chain info container for the given canonical head and finalized header if it
28 /// exists.
29pub fn new(
30 head: SealedHeader<N::BlockHeader>,
31 finalized: Option<SealedHeader<N::BlockHeader>>,
32 safe: Option<SealedHeader<N::BlockHeader>>,
33 ) -> Self {
34let (finalized_block, _) = watch::channel(finalized);
35let (safe_block, _) = watch::channel(safe);
3637Self {
38 inner: Arc::new(ChainInfoInner {
39 last_forkchoice_update: RwLock::new(None),
4041 canonical_head_number: AtomicU64::new(head.number()),
42 canonical_head: RwLock::new(head),
43safe_block,
44finalized_block,
45 }),
46 }
47 }
4849/// Returns the [`ChainInfo`] for the canonical head.
50pub fn chain_info(&self) -> ChainInfo {
51let inner = self.inner.canonical_head.read();
52 ChainInfo { best_hash: inner.hash(), best_number: inner.number() }
53 }
5455/// Update the timestamp when we received a forkchoice update.
56pub fn on_forkchoice_update_received(&self) {
57self.inner.last_forkchoice_update.write().replace(Instant::now());
58 }
5960/// Returns the instant when we received the latest forkchoice update.
61pub fn last_forkchoice_update_received_at(&self) -> Option<Instant> {
62*self.inner.last_forkchoice_update.read()
63 }
6465/// Returns the canonical head of the chain.
66pub fn get_canonical_head(&self) -> SealedHeader<N::BlockHeader> {
67self.inner.canonical_head.read().clone()
68 }
6970/// Returns the safe header of the chain.
71pub fn get_safe_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
72self.inner.safe_block.borrow().clone()
73 }
7475/// Returns the finalized header of the chain.
76pub fn get_finalized_header(&self) -> Option<SealedHeader<N::BlockHeader>> {
77self.inner.finalized_block.borrow().clone()
78 }
7980/// Returns the canonical head of the chain.
81pub fn get_canonical_num_hash(&self) -> BlockNumHash {
82self.inner.canonical_head.read().num_hash()
83 }
8485/// Returns the canonical head of the chain.
86pub fn get_canonical_block_number(&self) -> BlockNumber {
87self.inner.canonical_head_number.load(Ordering::Relaxed)
88 }
8990/// Returns the safe header of the chain.
91pub fn get_safe_num_hash(&self) -> Option<BlockNumHash> {
92self.inner.safe_block.borrow().as_ref().map(SealedHeader::num_hash)
93 }
9495/// Returns the finalized header of the chain.
96pub fn get_finalized_num_hash(&self) -> Option<BlockNumHash> {
97self.inner.finalized_block.borrow().as_ref().map(SealedHeader::num_hash)
98 }
99100/// Sets the canonical head of the chain.
101pub fn set_canonical_head(&self, header: SealedHeader<N::BlockHeader>) {
102let number = header.number();
103*self.inner.canonical_head.write() = header;
104105// also update the atomic number.
106self.inner.canonical_head_number.store(number, Ordering::Relaxed);
107 }
108109/// Sets the safe header of the chain.
110pub fn set_safe(&self, header: SealedHeader<N::BlockHeader>) {
111self.inner.safe_block.send_if_modified(|current_header| {
112if current_header.as_ref().map(SealedHeader::hash) != Some(header.hash()) {
113let _ = current_header.replace(header);
114return true
115}
116117false
118});
119 }
120121/// Sets the finalized header of the chain.
122pub fn set_finalized(&self, header: SealedHeader<N::BlockHeader>) {
123self.inner.finalized_block.send_if_modified(|current_header| {
124if current_header.as_ref().map(SealedHeader::hash) != Some(header.hash()) {
125let _ = current_header.replace(header);
126return true
127}
128129false
130});
131 }
132133/// Subscribe to the finalized block.
134pub fn subscribe_finalized_block(
135&self,
136 ) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
137self.inner.finalized_block.subscribe()
138 }
139140/// Subscribe to the safe block.
141pub fn subscribe_safe_block(&self) -> watch::Receiver<Option<SealedHeader<N::BlockHeader>>> {
142self.inner.safe_block.subscribe()
143 }
144}
145146/// Container type for all chain info fields
147#[derive(Debug)]
148struct ChainInfoInner<N: NodePrimitives = reth_ethereum_primitives::EthPrimitives> {
149/// Timestamp when we received the last fork choice update.
150 ///
151 /// This is mainly used to track if we're connected to a beacon node.
152last_forkchoice_update: RwLock<Option<Instant>>,
153154/// Tracks the number of the `canonical_head`.
155canonical_head_number: AtomicU64,
156/// The canonical head of the chain.
157canonical_head: RwLock<SealedHeader<N::BlockHeader>>,
158/// The block that the beacon node considers safe.
159safe_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
160/// The block that the beacon node considers finalized.
161finalized_block: watch::Sender<Option<SealedHeader<N::BlockHeader>>>,
162}
163164#[cfg(test)]
165mod tests {
166use super::*;
167use reth_ethereum_primitives::EthPrimitives;
168use reth_testing_utils::{generators, generators::random_header};
169170#[test]
171fn test_chain_info() {
172// Create a random header
173let mut rng = generators::rng();
174let header = random_header(&mut rng, 10, None);
175176// Create a new chain info tracker with the header
177let tracker: ChainInfoTracker<EthPrimitives> =
178 ChainInfoTracker::new(header.clone(), None, None);
179180// Fetch the chain information from the tracker
181let chain_info = tracker.chain_info();
182183// Verify that the chain information matches the header
184assert_eq!(chain_info.best_number, header.number);
185assert_eq!(chain_info.best_hash, header.hash());
186 }
187188#[test]
189fn test_on_forkchoice_update_received() {
190// Create a random block header
191let mut rng = generators::rng();
192let header = random_header(&mut rng, 10, None);
193194// Create a new chain info tracker with the header
195let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header, None, None);
196197// Assert that there has been no forkchoice update yet (the timestamp is None)
198assert!(tracker.last_forkchoice_update_received_at().is_none());
199200// Call the method to record the receipt of a forkchoice update
201tracker.on_forkchoice_update_received();
202203// Assert that there is now a timestamp indicating when the forkchoice update was received
204assert!(tracker.last_forkchoice_update_received_at().is_some());
205 }
206207#[test]
208fn test_set_canonical_head() {
209// Create a random number generator
210let mut rng = generators::rng();
211// Generate two random headers for testing
212let header1 = random_header(&mut rng, 10, None);
213let header2 = random_header(&mut rng, 20, None);
214215// Create a new chain info tracker with the first header
216let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
217218// Set the second header as the canonical head of the tracker
219tracker.set_canonical_head(header2.clone());
220221// Assert that the tracker now uses the second header as its canonical head
222let canonical_head = tracker.get_canonical_head();
223assert_eq!(canonical_head, header2);
224 }
225226#[test]
227fn test_set_safe() {
228// Create a random number generator
229let mut rng = generators::rng();
230231// Case 1: basic test
232 // Generate two random headers for the test
233let header1 = random_header(&mut rng, 10, None);
234let header2 = random_header(&mut rng, 20, None);
235236// Create a new chain info tracker with the first header (header1)
237let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
238239// Call the set_safe method with the second header (header2)
240tracker.set_safe(header2.clone());
241242// Verify that the tracker now has header2 as the safe block
243let safe_header = tracker.get_safe_header();
244assert!(safe_header.is_some()); // Ensure a safe header is present
245let safe_header = safe_header.unwrap();
246assert_eq!(safe_header, header2);
247248// Case 2: call with the same header as the current safe block
249 // Call set_safe again with the same header (header2)
250tracker.set_safe(header2.clone());
251252// Verify that nothing changes and the safe header remains the same
253let same_safe_header = tracker.get_safe_header();
254assert!(same_safe_header.is_some());
255let same_safe_header = same_safe_header.unwrap();
256assert_eq!(same_safe_header, header2);
257258// Case 3: call with a different (new) header
259 // Generate a third header with a higher block number
260let header3 = random_header(&mut rng, 30, None);
261262// Call set_safe with this new header (header3)
263tracker.set_safe(header3.clone());
264265// Verify that the safe header is updated with the new header
266let updated_safe_header = tracker.get_safe_header();
267assert!(updated_safe_header.is_some());
268let updated_safe_header = updated_safe_header.unwrap();
269assert_eq!(updated_safe_header, header3);
270 }
271272#[test]
273fn test_set_finalized() {
274// Create a random number generator
275let mut rng = generators::rng();
276277// Generate random headers for testing
278let header1 = random_header(&mut rng, 10, None);
279let header2 = random_header(&mut rng, 20, None);
280let header3 = random_header(&mut rng, 30, None);
281282// Create a new chain info tracker with the first header
283let tracker: ChainInfoTracker<EthPrimitives> = ChainInfoTracker::new(header1, None, None);
284285// Initial state: finalize header should be None
286assert!(tracker.get_finalized_header().is_none());
287288// Set the second header as the finalized header
289tracker.set_finalized(header2.clone());
290291// Assert that the tracker now uses the second header as its finalized block
292let finalized_header = tracker.get_finalized_header();
293assert!(finalized_header.is_some());
294let finalized_header = finalized_header.unwrap();
295assert_eq!(finalized_header, header2);
296297// Case 2: attempt to set the same finalized header again
298tracker.set_finalized(header2.clone());
299300// The finalized header should remain unchanged
301let unchanged_finalized_header = tracker.get_finalized_header();
302assert_eq!(unchanged_finalized_header.unwrap(), header2); // Should still be header2
303304 // Case 3: set a higher block number as finalized
305tracker.set_finalized(header3.clone());
306307// The finalized header should now be updated to header3
308let updated_finalized_header = tracker.get_finalized_header();
309assert!(updated_finalized_header.is_some());
310assert_eq!(updated_finalized_header.unwrap(), header3);
311 }
312313#[test]
314fn test_get_finalized_num_hash() {
315// Create a random header
316let mut rng = generators::rng();
317let finalized_header = random_header(&mut rng, 10, None);
318319// Create a new chain info tracker with the finalized header
320let tracker: ChainInfoTracker<EthPrimitives> =
321 ChainInfoTracker::new(finalized_header.clone(), Some(finalized_header.clone()), None);
322323// Assert that the BlockNumHash returned matches the finalized header
324assert_eq!(tracker.get_finalized_num_hash(), Some(finalized_header.num_hash()));
325 }
326327#[test]
328fn test_get_safe_num_hash() {
329// Create a random header
330let mut rng = generators::rng();
331let safe_header = random_header(&mut rng, 10, None);
332333// Create a new chain info tracker with the safe header
334let tracker: ChainInfoTracker<EthPrimitives> =
335 ChainInfoTracker::new(safe_header.clone(), None, None);
336 tracker.set_safe(safe_header.clone());
337338// Assert that the BlockNumHash returned matches the safe header
339assert_eq!(tracker.get_safe_num_hash(), Some(safe_header.num_hash()));
340 }
341}