reth_ethereum_forks/
forkcondition.rs

1use crate::Head;
2use alloy_primitives::{BlockNumber, U256};
3
4/// The condition at which a fork is activated.
5#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub enum ForkCondition {
8    /// The fork is activated after a certain block.
9    Block(BlockNumber),
10    /// The fork is activated after a total difficulty has been reached.
11    TTD {
12        /// The block number at which TTD is reached, if it is known.
13        ///
14        /// This should **NOT** be set unless you want this block advertised as [EIP-2124][eip2124]
15        /// `FORK_NEXT`. This is currently only the case for Sepolia and Holesky.
16        ///
17        /// [eip2124]: https://eips.ethereum.org/EIPS/eip-2124
18        fork_block: Option<BlockNumber>,
19        /// The total difficulty after which the fork is activated.
20        total_difficulty: U256,
21    },
22    /// The fork is activated after a specific timestamp.
23    Timestamp(u64),
24    /// The fork is never activated
25    #[default]
26    Never,
27}
28
29impl ForkCondition {
30    /// Returns true if the fork condition is timestamp based.
31    pub const fn is_timestamp(&self) -> bool {
32        matches!(self, Self::Timestamp(_))
33    }
34
35    /// Checks whether the fork condition is satisfied at the given block.
36    ///
37    /// For TTD conditions, this will only return true if the activation block is already known.
38    ///
39    /// For timestamp conditions, this will always return false.
40    pub const fn active_at_block(&self, current_block: BlockNumber) -> bool {
41        matches!(self, Self::Block(block)
42        | Self::TTD { fork_block: Some(block), .. } if current_block >= *block)
43    }
44
45    /// Checks if the given block is the first block that satisfies the fork condition.
46    ///
47    /// This will return false for any condition that is not block based.
48    pub const fn transitions_at_block(&self, current_block: BlockNumber) -> bool {
49        matches!(self, Self::Block(block) if current_block == *block)
50    }
51
52    /// Checks whether the fork condition is satisfied at the given total difficulty and difficulty
53    /// of a current block.
54    ///
55    /// The fork is considered active if the _previous_ total difficulty is above the threshold.
56    /// To achieve that, we subtract the passed `difficulty` from the current block's total
57    /// difficulty, and check if it's above the Fork Condition's total difficulty (here:
58    /// `58_750_000_000_000_000_000_000`)
59    ///
60    /// This will return false for any condition that is not TTD-based.
61    pub fn active_at_ttd(&self, ttd: U256, difficulty: U256) -> bool {
62        matches!(self, Self::TTD { total_difficulty, .. }
63            if ttd.saturating_sub(difficulty) >= *total_difficulty)
64    }
65
66    /// Checks whether the fork condition is satisfied at the given timestamp.
67    ///
68    /// This will return false for any condition that is not timestamp-based.
69    pub const fn active_at_timestamp(&self, timestamp: u64) -> bool {
70        matches!(self, Self::Timestamp(time) if timestamp >= *time)
71    }
72
73    /// Checks if the given block is the first block that satisfies the fork condition.
74    ///
75    /// This will return false for any condition that is not timestamp based.
76    pub const fn transitions_at_timestamp(&self, timestamp: u64, parent_timestamp: u64) -> bool {
77        matches!(self, Self::Timestamp(time) if timestamp >= *time && parent_timestamp < *time)
78    }
79
80    /// Checks whether the fork condition is satisfied at the given head block.
81    ///
82    /// This will return true if:
83    ///
84    /// - The condition is satisfied by the block number;
85    /// - The condition is satisfied by the timestamp;
86    /// - or the condition is satisfied by the total difficulty
87    pub fn active_at_head(&self, head: &Head) -> bool {
88        self.active_at_block(head.number) ||
89            self.active_at_timestamp(head.timestamp) ||
90            self.active_at_ttd(head.total_difficulty, head.difficulty)
91    }
92
93    /// Get the total terminal difficulty for this fork condition.
94    ///
95    /// Returns `None` for fork conditions that are not TTD based.
96    pub const fn ttd(&self) -> Option<U256> {
97        match self {
98            Self::TTD { total_difficulty, .. } => Some(*total_difficulty),
99            _ => None,
100        }
101    }
102
103    /// Returns the timestamp of the fork condition, if it is timestamp based.
104    pub const fn as_timestamp(&self) -> Option<u64> {
105        match self {
106            Self::Timestamp(timestamp) => Some(*timestamp),
107            _ => None,
108        }
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use alloy_primitives::U256;
116
117    #[test]
118    fn test_active_at_block() {
119        // Test if the condition is active at the current block number
120        let fork_condition = ForkCondition::Block(10);
121        assert!(fork_condition.active_at_block(10), "The condition should be active at block 10");
122
123        // Test if the condition is not active at a lower block number
124        assert!(
125            !fork_condition.active_at_block(9),
126            "The condition should not be active at block 9"
127        );
128
129        // Test if TTD-based condition with known block activates
130        let fork_condition =
131            ForkCondition::TTD { fork_block: Some(10), total_difficulty: U256::from(1000) };
132        assert!(
133            fork_condition.active_at_block(10),
134            "The TTD condition should be active at block 10"
135        );
136
137        // Test if TTD-based condition with unknown block does not activate
138        let fork_condition =
139            ForkCondition::TTD { fork_block: None, total_difficulty: U256::from(1000) };
140        assert!(
141            !fork_condition.active_at_block(10),
142            "The TTD condition should not be active at block 10 with an unknown block number"
143        );
144    }
145
146    #[test]
147    fn test_transitions_at_block() {
148        // Test if the condition transitions at the correct block number
149        let fork_condition = ForkCondition::Block(10);
150        assert!(
151            fork_condition.transitions_at_block(10),
152            "The condition should transition at block 10"
153        );
154
155        // Test if the condition does not transition at a different block number
156        assert!(
157            !fork_condition.transitions_at_block(9),
158            "The condition should not transition at a different block number"
159        );
160        assert!(
161            !fork_condition.transitions_at_block(11),
162            "The condition should not transition at a different block number"
163        );
164    }
165
166    #[test]
167    fn test_active_at_ttd() {
168        // Test if the condition activates at the correct total difficulty
169        let fork_condition =
170            ForkCondition::TTD { fork_block: Some(10), total_difficulty: U256::from(1000) };
171        assert!(
172            fork_condition.active_at_ttd(U256::from(1000000), U256::from(100)),
173            "The TTD condition should be active when the total difficulty matches"
174        );
175
176        // Test if the condition does not activate when the total difficulty is lower
177        assert!(
178            !fork_condition.active_at_ttd(U256::from(900), U256::from(100)),
179            "The TTD condition should not be active when the total difficulty is lower"
180        );
181
182        // Test with a saturated subtraction
183        assert!(
184            !fork_condition.active_at_ttd(U256::from(900), U256::from(1000)),
185            "The TTD condition should not be active when the subtraction saturates"
186        );
187    }
188
189    #[test]
190    fn test_active_at_timestamp() {
191        // Test if the condition activates at the correct timestamp
192        let fork_condition = ForkCondition::Timestamp(12345);
193        assert!(
194            fork_condition.active_at_timestamp(12345),
195            "The condition should be active at timestamp 12345"
196        );
197
198        // Test if the condition does not activate at an earlier timestamp
199        assert!(
200            !fork_condition.active_at_timestamp(12344),
201            "The condition should not be active at an earlier timestamp"
202        );
203    }
204
205    #[test]
206    fn test_transitions_at_timestamp() {
207        // Test if the condition transitions at the correct timestamp
208        let fork_condition = ForkCondition::Timestamp(12345);
209        assert!(
210            fork_condition.transitions_at_timestamp(12345, 12344),
211            "The condition should transition at timestamp 12345"
212        );
213
214        // Test if the condition does not transition if the parent timestamp is already the same
215        assert!(
216            !fork_condition.transitions_at_timestamp(12345, 12345),
217            "The condition should not transition if the parent timestamp is already 12345"
218        );
219        // Test with earlier timestamp
220        assert!(
221            !fork_condition.transitions_at_timestamp(123, 122),
222            "The condition should not transition if the parent timestamp is earlier"
223        );
224    }
225
226    #[test]
227    fn test_active_at_head() {
228        let head = Head {
229            hash: Default::default(),
230            number: 10,
231            timestamp: 12345,
232            total_difficulty: U256::from(1000),
233            difficulty: U256::from(100),
234        };
235
236        // Test if the condition activates based on block number
237        let fork_condition = ForkCondition::Block(10);
238        assert!(
239            fork_condition.active_at_head(&head),
240            "The condition should be active at the given head block number"
241        );
242        let fork_condition = ForkCondition::Block(11);
243        assert!(
244            !fork_condition.active_at_head(&head),
245            "The condition should not be active at the given head block number"
246        );
247
248        // Test if the condition activates based on timestamp
249        let fork_condition = ForkCondition::Timestamp(12345);
250        assert!(
251            fork_condition.active_at_head(&head),
252            "The condition should be active at the given head timestamp"
253        );
254        let fork_condition = ForkCondition::Timestamp(12346);
255        assert!(
256            !fork_condition.active_at_head(&head),
257            "The condition should not be active at the given head timestamp"
258        );
259
260        // Test if the condition activates based on total difficulty and block number
261        let fork_condition =
262            ForkCondition::TTD { fork_block: Some(9), total_difficulty: U256::from(900) };
263        assert!(
264            fork_condition.active_at_head(&head),
265            "The condition should be active at the given head total difficulty"
266        );
267        let fork_condition =
268            ForkCondition::TTD { fork_block: None, total_difficulty: U256::from(900) };
269        assert!(
270            fork_condition.active_at_head(&head),
271            "The condition should be active at the given head total difficulty as the block number is unknown"
272        );
273        let fork_condition =
274            ForkCondition::TTD { fork_block: Some(11), total_difficulty: U256::from(900) };
275        assert!(
276            fork_condition.active_at_head(&head),
277            "The condition should be active as the total difficulty is higher"
278        );
279        let fork_condition =
280            ForkCondition::TTD { fork_block: Some(10), total_difficulty: U256::from(9000) };
281        assert!(
282            fork_condition.active_at_head(&head),
283            "The condition should be active as the total difficulty is higher than head"
284        );
285    }
286}