reth_ethereum_forks/
forkcondition.rs1use crate::Head;
2use alloy_primitives::{BlockNumber, U256};
3
4#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub enum ForkCondition {
8 Block(BlockNumber),
10 TTD {
12 fork_block: Option<BlockNumber>,
19 total_difficulty: U256,
21 },
22 Timestamp(u64),
24 #[default]
26 Never,
27}
28
29impl ForkCondition {
30 pub const fn is_timestamp(&self) -> bool {
32 matches!(self, Self::Timestamp(_))
33 }
34
35 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 pub const fn transitions_at_block(&self, current_block: BlockNumber) -> bool {
49 matches!(self, Self::Block(block) if current_block == *block)
50 }
51
52 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 pub const fn active_at_timestamp(&self, timestamp: u64) -> bool {
70 matches!(self, Self::Timestamp(time) if timestamp >= *time)
71 }
72
73 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 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 pub const fn ttd(&self) -> Option<U256> {
97 match self {
98 Self::TTD { total_difficulty, .. } => Some(*total_difficulty),
99 _ => None,
100 }
101 }
102
103 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 let fork_condition = ForkCondition::Block(10);
121 assert!(fork_condition.active_at_block(10), "The condition should be active at block 10");
122
123 assert!(
125 !fork_condition.active_at_block(9),
126 "The condition should not be active at block 9"
127 );
128
129 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 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 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 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 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 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 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 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 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 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 assert!(
216 !fork_condition.transitions_at_timestamp(12345, 12345),
217 "The condition should not transition if the parent timestamp is already 12345"
218 );
219 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 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 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 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}