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
11const INVALID_HEADER_HIT_EVICTION_THRESHOLD: u8 = 128;
16
17#[derive(Debug)]
19pub struct InvalidHeaderCache {
20 headers: LruMap<B256, HeaderEntry>,
22 metrics: InvalidHeaderCacheMetrics,
24}
25
26impl InvalidHeaderCache {
27 pub fn new(max_length: u32) -> Self {
29 Self { headers: LruMap::new(ByLength::new(max_length)), metrics: Default::default() }
30 }
31
32 fn insert_entry(&mut self, hash: B256, header: BlockWithParent) {
33 self.headers.insert(hash, HeaderEntry { header, hit_count: 0 });
34 }
35
36 pub fn get(&mut self, hash: &B256) -> Option<BlockWithParent> {
41 {
42 let entry = self.headers.get(hash)?;
43 entry.hit_count += 1;
44 if entry.hit_count < INVALID_HEADER_HIT_EVICTION_THRESHOLD {
45 return Some(entry.header)
46 }
47 }
48 self.headers.remove(hash);
50 self.metrics.hit_evictions.increment(1);
51 self.metrics.count.set(self.headers.len() as f64);
52 None
53 }
54
55 pub fn insert_with_invalid_ancestor(
57 &mut self,
58 header_hash: B256,
59 invalid_ancestor: BlockWithParent,
60 ) {
61 if self.get(&header_hash).is_none() {
62 warn!(target: "consensus::engine", hash=?header_hash, ?invalid_ancestor, "Bad block with existing invalid ancestor");
63 self.insert_entry(header_hash, invalid_ancestor);
64
65 self.metrics.known_ancestor_inserts.increment(1);
67 self.metrics.count.set(self.headers.len() as f64);
68 }
69 }
70
71 pub fn insert(&mut self, invalid_ancestor: BlockWithParent) {
73 if self.get(&invalid_ancestor.block.hash).is_none() {
74 warn!(target: "consensus::engine", ?invalid_ancestor, "Bad block with hash");
75 self.insert_entry(invalid_ancestor.block.hash, invalid_ancestor);
76
77 self.metrics.unique_inserts.increment(1);
79 self.metrics.count.set(self.headers.len() as f64);
80 }
81 }
82}
83
84struct HeaderEntry {
85 hit_count: u8,
87 header: BlockWithParent,
89}
90
91#[derive(Metrics)]
93#[metrics(scope = "consensus.engine.beacon.invalid_headers")]
94struct InvalidHeaderCacheMetrics {
95 count: Gauge,
97 known_ancestor_inserts: Counter,
99 unique_inserts: Counter,
101 hit_evictions: Counter,
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use alloy_consensus::Header;
109 use reth_primitives_traits::SealedHeader;
110
111 #[test]
112 fn test_hit_eviction() {
113 let mut cache = InvalidHeaderCache::new(10);
114 let header = Header::default();
115 let header = SealedHeader::seal_slow(header);
116 cache.insert(header.block_with_parent());
117 assert_eq!(cache.headers.get(&header.hash()).unwrap().hit_count, 0);
118
119 for hit in 1..INVALID_HEADER_HIT_EVICTION_THRESHOLD {
120 assert!(cache.get(&header.hash()).is_some());
121 assert_eq!(cache.headers.get(&header.hash()).unwrap().hit_count, hit);
122 }
123
124 assert!(cache.get(&header.hash()).is_none());
125 }
126}