reth_ethereum_forks/hardforks/
mod.rs

1mod dev;
2pub use dev::DEV_HARDFORKS;
3
4use crate::{ForkCondition, ForkFilter, ForkId, Hardfork, Head};
5#[cfg(feature = "std")]
6use rustc_hash::FxHashMap;
7
8use alloc::{boxed::Box, vec::Vec};
9
10/// Generic trait over a set of ordered hardforks
11#[auto_impl::auto_impl(&, Arc)]
12pub trait Hardforks: Clone {
13    /// Retrieves [`ForkCondition`] from `fork`. If `fork` is not present, returns
14    /// [`ForkCondition::Never`].
15    fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition;
16
17    /// Get an iterator of all hardforks with their respective activation conditions.
18    fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)>;
19
20    /// Convenience method to check if a fork is active at a given timestamp.
21    fn is_fork_active_at_timestamp<H: Hardfork>(&self, fork: H, timestamp: u64) -> bool {
22        self.fork(fork).active_at_timestamp(timestamp)
23    }
24
25    /// Convenience method to check if a fork is active at a given block number.
26    fn is_fork_active_at_block<H: Hardfork>(&self, fork: H, block_number: u64) -> bool {
27        self.fork(fork).active_at_block(block_number)
28    }
29
30    /// Compute the [`ForkId`] for the given [`Head`] following eip-6122 spec
31    fn fork_id(&self, head: &Head) -> ForkId;
32
33    /// Returns the [`ForkId`] for the last fork.
34    ///
35    /// NOTE: This returns the latest implemented [`ForkId`]. In many cases this will be the future
36    /// [`ForkId`] on given network.
37    fn latest_fork_id(&self) -> ForkId;
38
39    /// Creates a [`ForkFilter`] for the block described by [Head].
40    fn fork_filter(&self, head: Head) -> ForkFilter;
41}
42
43/// Ordered list of a chain hardforks that implement [`Hardfork`].
44#[derive(Default, Clone, PartialEq, Eq)]
45pub struct ChainHardforks {
46    forks: Vec<(Box<dyn Hardfork>, ForkCondition)>,
47    #[cfg(feature = "std")]
48    map: FxHashMap<&'static str, ForkCondition>,
49    #[cfg(not(feature = "std"))]
50    map: alloc::collections::BTreeMap<&'static str, ForkCondition>,
51}
52
53impl ChainHardforks {
54    /// Creates a new [`ChainHardforks`] from a list which **must be ordered** by activation.
55    ///
56    /// Equivalent Ethereum hardforks **must be included** as well.
57    pub fn new(forks: Vec<(Box<dyn Hardfork>, ForkCondition)>) -> Self {
58        let map = forks.iter().map(|(fork, condition)| (fork.name(), *condition)).collect();
59
60        Self { forks, map }
61    }
62
63    /// Total number of hardforks.
64    pub fn len(&self) -> usize {
65        self.forks.len()
66    }
67
68    /// Checks if the fork list is empty.
69    pub fn is_empty(&self) -> bool {
70        self.forks.is_empty()
71    }
72
73    /// Retrieves [`ForkCondition`] from `fork`. If `fork` is not present, returns
74    /// [`ForkCondition::Never`].
75    pub fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition {
76        self.get(fork).unwrap_or_default()
77    }
78
79    /// Retrieves [`ForkCondition`] from `fork` if it exists, otherwise `None`.
80    pub fn get<H: Hardfork>(&self, fork: H) -> Option<ForkCondition> {
81        self.map.get(fork.name()).copied()
82    }
83
84    /// Retrieves the fork block number or timestamp from `fork` if it exists, otherwise `None`.
85    pub fn fork_block<H: Hardfork>(&self, fork: H) -> Option<u64> {
86        match self.fork(fork) {
87            ForkCondition::Block(block) => Some(block),
88            ForkCondition::TTD { fork_block, .. } => fork_block,
89            ForkCondition::Timestamp(ts) => Some(ts),
90            ForkCondition::Never => None,
91        }
92    }
93
94    /// Get an iterator of all hardforks with their respective activation conditions.
95    pub fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
96        self.forks.iter().map(|(f, b)| (&**f, *b))
97    }
98
99    /// Get last hardfork from the list.
100    pub fn last(&self) -> Option<(Box<dyn Hardfork>, ForkCondition)> {
101        self.forks.last().map(|(f, b)| (f.clone(), *b))
102    }
103
104    /// Convenience method to check if a fork is active at a given timestamp.
105    pub fn is_fork_active_at_timestamp<H: Hardfork>(&self, fork: H, timestamp: u64) -> bool {
106        self.fork(fork).active_at_timestamp(timestamp)
107    }
108
109    /// Convenience method to check if a fork is active at a given block number.
110    pub fn is_fork_active_at_block<H: Hardfork>(&self, fork: H, block_number: u64) -> bool {
111        self.fork(fork).active_at_block(block_number)
112    }
113
114    /// Inserts a fork with the given [`ForkCondition`], maintaining forks in ascending order
115    /// based on the `Ord` implementation of [`ForkCondition`].
116    ///
117    /// If the fork already exists (regardless of its current condition type), it will be removed
118    /// and re-inserted at the appropriate position based on the new condition.
119    ///
120    /// # Ordering Behavior
121    ///
122    /// Forks are ordered according to [`ForkCondition`]'s `Ord` implementation:
123    /// - [`ForkCondition::Never`] comes first
124    /// - [`ForkCondition::Block`] ordered by block number
125    /// - [`ForkCondition::Timestamp`] ordered by timestamp value
126    /// - [`ForkCondition::TTD`] ordered by total difficulty
127    ///
128    /// # Example
129    ///
130    /// ```ignore
131    /// let mut forks = ChainHardforks::default();
132    /// forks.insert(Fork::Frontier, ForkCondition::Block(0));
133    /// forks.insert(Fork::Homestead, ForkCondition::Block(1_150_000));
134    /// forks.insert(Fork::Cancun, ForkCondition::Timestamp(1710338135));
135    ///
136    /// // Forks are ordered: Frontier (Block 0), Homestead (Block 1150000), Cancun (Timestamp)
137    /// ```
138    pub fn insert<H: Hardfork>(&mut self, fork: H, condition: ForkCondition) {
139        // Remove existing fork if it exists
140        self.remove(&fork);
141
142        // Find the correct position based on ForkCondition's Ord implementation
143        let pos = self
144            .forks
145            .iter()
146            .position(|(_, existing_condition)| *existing_condition > condition)
147            .unwrap_or(self.forks.len());
148
149        self.map.insert(fork.name(), condition);
150        self.forks.insert(pos, (Box::new(fork), condition));
151    }
152
153    /// Extends the list with multiple forks, updating existing entries with new
154    /// [`ForkCondition`]s if they already exist.
155    ///
156    /// Each fork is inserted using [`Self::insert`], maintaining proper ordering based on
157    /// [`ForkCondition`]'s `Ord` implementation.
158    ///
159    /// # Example
160    ///
161    /// ```ignore
162    /// let mut forks = ChainHardforks::default();
163    /// forks.extend([
164    ///     (Fork::Homestead, ForkCondition::Block(1_150_000)),
165    ///     (Fork::Frontier, ForkCondition::Block(0)),
166    ///     (Fork::Cancun, ForkCondition::Timestamp(1710338135)),
167    /// ]);
168    ///
169    /// // Forks will be automatically ordered: Frontier, Homestead, Cancun
170    /// ```
171    pub fn extend<H: Hardfork + Clone>(
172        &mut self,
173        forks: impl IntoIterator<Item = (H, ForkCondition)>,
174    ) {
175        for (fork, condition) in forks {
176            self.insert(fork, condition);
177        }
178    }
179
180    /// Removes `fork` from list.
181    pub fn remove<H: Hardfork>(&mut self, fork: &H) {
182        self.forks.retain(|(inner_fork, _)| inner_fork.name() != fork.name());
183        self.map.remove(fork.name());
184    }
185}
186
187impl core::fmt::Debug for ChainHardforks {
188    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
189        f.debug_struct("ChainHardforks")
190            .field("0", &self.forks_iter().map(|(hf, cond)| (hf.name(), cond)).collect::<Vec<_>>())
191            .finish()
192    }
193}
194
195impl<T: Hardfork, const N: usize> From<[(T, ForkCondition); N]> for ChainHardforks {
196    fn from(list: [(T, ForkCondition); N]) -> Self {
197        Self::new(
198            list.into_iter()
199                .map(|(fork, cond)| (Box::new(fork) as Box<dyn Hardfork>, cond))
200                .collect(),
201        )
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use alloy_hardforks::hardfork;
209
210    hardfork!(AHardfork { A1, A2, A3 });
211    hardfork!(BHardfork { B1, B2 });
212
213    #[test]
214    fn add_hardforks() {
215        let mut forks = ChainHardforks::default();
216        forks.insert(AHardfork::A1, ForkCondition::Block(1));
217        forks.insert(BHardfork::B1, ForkCondition::Block(1));
218        assert_eq!(forks.len(), 2);
219        forks.is_fork_active_at_block(AHardfork::A1, 1);
220        forks.is_fork_active_at_block(BHardfork::B1, 1);
221    }
222
223    #[test]
224    fn insert_maintains_fork_order() {
225        let mut forks = ChainHardforks::default();
226
227        // Insert forks in random order
228        forks.insert(BHardfork::B1, ForkCondition::Timestamp(2000));
229        forks.insert(AHardfork::A1, ForkCondition::Block(100));
230        forks.insert(AHardfork::A2, ForkCondition::Block(50));
231        forks.insert(BHardfork::B2, ForkCondition::Timestamp(1000));
232
233        assert_eq!(forks.len(), 4);
234
235        let fork_list: Vec<_> = forks.forks_iter().collect();
236
237        // Verify ordering: Block conditions come before Timestamp conditions
238        // and within each type, they're ordered by value
239        assert_eq!(fork_list[0].0.name(), "A2");
240        assert_eq!(fork_list[0].1, ForkCondition::Block(50));
241        assert_eq!(fork_list[1].0.name(), "A1");
242        assert_eq!(fork_list[1].1, ForkCondition::Block(100));
243        assert_eq!(fork_list[2].0.name(), "B2");
244        assert_eq!(fork_list[2].1, ForkCondition::Timestamp(1000));
245        assert_eq!(fork_list[3].0.name(), "B1");
246        assert_eq!(fork_list[3].1, ForkCondition::Timestamp(2000));
247    }
248
249    #[test]
250    fn insert_replaces_and_reorders_existing_fork() {
251        let mut forks = ChainHardforks::default();
252
253        // Insert initial forks
254        forks.insert(AHardfork::A1, ForkCondition::Block(100));
255        forks.insert(BHardfork::B1, ForkCondition::Block(200));
256        forks.insert(AHardfork::A2, ForkCondition::Timestamp(1000));
257
258        assert_eq!(forks.len(), 3);
259
260        // Update A1 from Block to Timestamp - should move it after B1
261        forks.insert(AHardfork::A1, ForkCondition::Timestamp(500));
262        assert_eq!(forks.len(), 3);
263
264        let fork_list: Vec<_> = forks.forks_iter().collect();
265
266        // Verify new ordering
267        assert_eq!(fork_list[0].0.name(), "B1");
268        assert_eq!(fork_list[0].1, ForkCondition::Block(200));
269        assert_eq!(fork_list[1].0.name(), "A1");
270        assert_eq!(fork_list[1].1, ForkCondition::Timestamp(500));
271        assert_eq!(fork_list[2].0.name(), "A2");
272        assert_eq!(fork_list[2].1, ForkCondition::Timestamp(1000));
273
274        // Update A1 timestamp to move it after A2
275        forks.insert(AHardfork::A1, ForkCondition::Timestamp(2000));
276        assert_eq!(forks.len(), 3);
277
278        let fork_list: Vec<_> = forks.forks_iter().collect();
279
280        assert_eq!(fork_list[0].0.name(), "B1");
281        assert_eq!(fork_list[0].1, ForkCondition::Block(200));
282        assert_eq!(fork_list[1].0.name(), "A2");
283        assert_eq!(fork_list[1].1, ForkCondition::Timestamp(1000));
284        assert_eq!(fork_list[2].0.name(), "A1");
285        assert_eq!(fork_list[2].1, ForkCondition::Timestamp(2000));
286    }
287
288    #[test]
289    fn extend_maintains_order() {
290        let mut forks = ChainHardforks::default();
291
292        // Use extend to insert multiple forks at once in random order
293        forks.extend([
294            (AHardfork::A1, ForkCondition::Block(100)),
295            (AHardfork::A2, ForkCondition::Timestamp(1000)),
296        ]);
297        forks.extend([(BHardfork::B1, ForkCondition::Timestamp(2000))]);
298
299        assert_eq!(forks.len(), 3);
300
301        let fork_list: Vec<_> = forks.forks_iter().collect();
302
303        // Verify ordering is maintained
304        assert_eq!(fork_list[0].0.name(), "A1");
305        assert_eq!(fork_list[0].1, ForkCondition::Block(100));
306        assert_eq!(fork_list[1].0.name(), "A2");
307        assert_eq!(fork_list[1].1, ForkCondition::Timestamp(1000));
308        assert_eq!(fork_list[2].0.name(), "B1");
309        assert_eq!(fork_list[2].1, ForkCondition::Timestamp(2000));
310
311        // Extend again with an update to A2
312        forks.extend([(AHardfork::A2, ForkCondition::Timestamp(3000))]);
313        assert_eq!(forks.len(), 3);
314
315        let fork_list: Vec<_> = forks.forks_iter().collect();
316
317        assert_eq!(fork_list[0].0.name(), "A1");
318        assert_eq!(fork_list[1].0.name(), "B1");
319        assert_eq!(fork_list[2].0.name(), "A2");
320        assert_eq!(fork_list[2].1, ForkCondition::Timestamp(3000));
321    }
322}