reth_engine_tree/tree/
invalid_headers.rs1use alloy_eips::eip1898::BlockWithParent;
2use alloy_primitives::B256;
3use reth_metrics::{
4 metrics::{Counter, Gauge},
5 Metrics,
6};
7use schnellru::{ByLength, LruMap};
8use std::fmt::Debug;
9use tracing::warn;
10
11#[derive(Debug)]
13pub struct InvalidHeaderCache {
14 headers: LruMap<B256, HeaderEntry>,
16 hit_eviction_threshold: u8,
18 metrics: InvalidHeaderCacheMetrics,
20}
21
22impl InvalidHeaderCache {
23 pub fn new(max_length: u32, hit_eviction_threshold: u8) -> Self {
28 Self {
29 headers: LruMap::new(ByLength::new(max_length)),
30 hit_eviction_threshold,
31 metrics: Default::default(),
32 }
33 }
34
35 fn insert_entry(&mut self, hash: B256, header: BlockWithParent) {
36 self.headers.insert(hash, HeaderEntry { header, hit_count: 0 });
37 }
38
39 pub fn get(&mut self, hash: &B256) -> Option<BlockWithParent> {
44 {
45 let entry = self.headers.get(hash)?;
46 entry.hit_count += 1;
47 if entry.hit_count < self.hit_eviction_threshold {
48 return Some(entry.header)
49 }
50 }
51 self.headers.remove(hash);
53 self.metrics.hit_evictions.increment(1);
54 self.metrics.count.set(self.headers.len() as f64);
55 None
56 }
57
58 pub fn insert_with_invalid_ancestor(
60 &mut self,
61 header_hash: B256,
62 invalid_ancestor: BlockWithParent,
63 ) {
64 if self.get(&header_hash).is_none() {
65 warn!(target: "consensus::engine", hash=?header_hash, ?invalid_ancestor, "Bad block with existing invalid ancestor");
66 self.insert_entry(header_hash, invalid_ancestor);
67
68 self.metrics.known_ancestor_inserts.increment(1);
70 self.metrics.count.set(self.headers.len() as f64);
71 }
72 }
73
74 pub fn insert(&mut self, invalid_ancestor: BlockWithParent) {
76 if self.get(&invalid_ancestor.block.hash).is_none() {
77 warn!(target: "consensus::engine", ?invalid_ancestor, "Bad block with hash");
78 self.insert_entry(invalid_ancestor.block.hash, invalid_ancestor);
79
80 self.metrics.unique_inserts.increment(1);
82 self.metrics.count.set(self.headers.len() as f64);
83 }
84 }
85}
86
87struct HeaderEntry {
88 hit_count: u8,
90 header: BlockWithParent,
92}
93
94#[derive(Metrics)]
96#[metrics(scope = "consensus.engine.beacon.invalid_headers")]
97struct InvalidHeaderCacheMetrics {
98 count: Gauge,
100 known_ancestor_inserts: Counter,
102 unique_inserts: Counter,
104 hit_evictions: Counter,
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use alloy_consensus::Header;
112 use reth_primitives_traits::SealedHeader;
113
114 #[test]
115 fn test_hit_eviction() {
116 let hit_eviction_threshold = 3;
117 let mut cache = InvalidHeaderCache::new(10, hit_eviction_threshold);
118 let header = Header::default();
119 let header = SealedHeader::seal_slow(header);
120 cache.insert(header.block_with_parent());
121 assert_eq!(cache.headers.get(&header.hash()).unwrap().hit_count, 0);
122
123 for hit in 1..hit_eviction_threshold {
124 assert!(cache.get(&header.hash()).is_some());
125 assert_eq!(cache.headers.get(&header.hash()).unwrap().hit_count, hit);
126 }
127
128 assert!(cache.get(&header.hash()).is_none());
129 }
130
131 #[test]
132 fn test_zero_hit_eviction_threshold_effectively_disables_cache() {
133 let mut cache = InvalidHeaderCache::new(10, 0);
134 let header = SealedHeader::seal_slow(Header::default());
135 cache.insert(header.block_with_parent());
136
137 assert!(cache.get(&header.hash()).is_none());
138 assert_eq!(cache.headers.len(), 0);
139 }
140}