1use core::fmt;
2
3use eth_valkyoth_primitives::{BlockNumber, ChainId, UnixTimestamp};
4
5#[non_exhaustive]
7#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
8pub enum Hardfork {
9 Frontier,
11 Homestead,
13 Byzantium,
15 London,
17 Shanghai,
19 Cancun,
21 Prague,
23 Amsterdam,
25}
26
27#[non_exhaustive]
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum ForkError {
31 Unsupported,
33 Inactive,
35 MissingActivation,
37 ChainMismatch,
39 DuplicateFork,
41 NonMonotonicForkOrder,
43}
44
45impl ForkError {
46 #[must_use]
48 pub const fn code(self) -> &'static str {
49 match self {
50 Self::Unsupported => "ETH_FORK_UNSUPPORTED",
51 Self::Inactive => "ETH_FORK_INACTIVE",
52 Self::MissingActivation => "ETH_FORK_MISSING_ACTIVATION",
53 Self::ChainMismatch => "ETH_FORK_CHAIN_MISMATCH",
54 Self::DuplicateFork => "ETH_FORK_DUPLICATE",
55 Self::NonMonotonicForkOrder => "ETH_FORK_NON_MONOTONIC_ORDER",
56 }
57 }
58
59 #[must_use]
61 pub const fn message(self) -> &'static str {
62 match self {
63 Self::Unsupported => "fork is not supported by this crate version",
64 Self::Inactive => "fork is not active for the validation context",
65 Self::MissingActivation => "fork activation data is incomplete",
66 Self::ChainMismatch => "fork chain id does not match the selected chain spec",
67 Self::DuplicateFork => "chain spec contains a duplicate hardfork entry",
68 Self::NonMonotonicForkOrder => "chain spec fork entries are not monotonic",
69 }
70 }
71}
72
73impl fmt::Display for ForkError {
74 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
75 formatter.write_str(self.message())
76 }
77}
78
79#[cfg(feature = "std")]
80impl std::error::Error for ForkError {}
81
82#[non_exhaustive]
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
85pub enum ForkActivation {
86 BlockOnly {
88 activation_block: BlockNumber,
90 },
91 BlockAndTimestamp {
93 activation_block: BlockNumber,
95 activation_timestamp: UnixTimestamp,
97 },
98}
99
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub struct ForkSpec {
103 pub chain_id: ChainId,
105 pub hardfork: Hardfork,
107 pub activation: ForkActivation,
109}
110
111impl ForkSpec {
112 #[must_use]
114 pub fn is_active_at(self, block_number: BlockNumber, timestamp: UnixTimestamp) -> bool {
115 match self.activation {
116 ForkActivation::BlockOnly { activation_block } => block_number >= activation_block,
117 ForkActivation::BlockAndTimestamp {
118 activation_block,
119 activation_timestamp,
120 } => block_number >= activation_block && timestamp >= activation_timestamp,
121 }
122 }
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
127pub struct ChainSpec<'a> {
128 chain_id: ChainId,
129 forks: &'a [ForkSpec],
130}
131
132impl<'a> ChainSpec<'a> {
133 #[must_use]
141 pub const fn new(chain_id: ChainId, forks: &'a [ForkSpec]) -> Self {
142 Self { chain_id, forks }
143 }
144
145 pub fn try_new(chain_id: ChainId, forks: &'a [ForkSpec]) -> Result<Self, ForkError> {
147 let spec = Self { chain_id, forks };
148 spec.validate()?;
149 Ok(spec)
150 }
151
152 #[must_use]
154 pub const fn chain_id(self) -> ChainId {
155 self.chain_id
156 }
157
158 #[must_use]
160 pub const fn forks(self) -> &'a [ForkSpec] {
161 self.forks
162 }
163
164 pub fn fork_spec(self, hardfork: Hardfork) -> Result<ForkSpec, ForkError> {
166 self.validate()?;
167 for fork in self.forks {
168 if fork.hardfork == hardfork {
169 return Ok(*fork);
170 }
171 }
172 Err(ForkError::Unsupported)
173 }
174
175 pub fn validation_context(
177 self,
178 hardfork: Hardfork,
179 block_number: BlockNumber,
180 timestamp: UnixTimestamp,
181 ) -> Result<ValidationContext, ForkError> {
182 let fork = self.fork_spec(hardfork)?;
183 let context = ValidationContext {
184 fork,
185 block_number,
186 timestamp,
187 };
188 if context.fork_is_active() {
189 Ok(context)
190 } else {
191 Err(ForkError::Inactive)
192 }
193 }
194
195 pub fn active_fork(
197 self,
198 block_number: BlockNumber,
199 timestamp: UnixTimestamp,
200 ) -> Result<ForkSpec, ForkError> {
201 let mut active: Option<ForkSpec> = None;
202 self.validate()?;
203 for fork in self.forks {
204 if fork.is_active_at(block_number, timestamp) {
205 active = match active {
206 Some(previous) if previous.hardfork > fork.hardfork => Some(previous),
207 _ => Some(*fork),
208 };
209 }
210 }
211 active.ok_or(ForkError::Inactive)
212 }
213
214 fn validate(self) -> Result<(), ForkError> {
215 let mut previous: Option<ForkSpec> = None;
216 for fork in self.forks {
217 if fork.chain_id != self.chain_id {
218 return Err(ForkError::ChainMismatch);
219 }
220 if let Some(previous_fork) = previous {
221 if fork.hardfork == previous_fork.hardfork {
222 return Err(ForkError::DuplicateFork);
223 }
224 if fork.hardfork < previous_fork.hardfork
225 || !fork_activation_is_monotonic(previous_fork.activation, fork.activation)
226 {
227 return Err(ForkError::NonMonotonicForkOrder);
228 }
229 }
230 previous = Some(*fork);
231 }
232 Ok(())
233 }
234}
235
236fn fork_activation_is_monotonic(previous: ForkActivation, next: ForkActivation) -> bool {
237 let previous_block = fork_activation_block(previous);
238 let next_block = fork_activation_block(next);
239 if next_block < previous_block {
240 return false;
241 }
242
243 match (previous, next) {
247 (
248 ForkActivation::BlockAndTimestamp {
249 activation_timestamp: previous_timestamp,
250 ..
251 },
252 ForkActivation::BlockAndTimestamp {
253 activation_timestamp: next_timestamp,
254 ..
255 },
256 ) => next_timestamp >= previous_timestamp,
257 _ => true,
258 }
259}
260
261fn fork_activation_block(activation: ForkActivation) -> BlockNumber {
262 match activation {
263 ForkActivation::BlockOnly { activation_block }
264 | ForkActivation::BlockAndTimestamp {
265 activation_block, ..
266 } => activation_block,
267 }
268}
269
270#[derive(Clone, Copy, Debug, Eq, PartialEq)]
272pub struct ValidationContext {
273 pub fork: ForkSpec,
275 pub block_number: BlockNumber,
277 pub timestamp: UnixTimestamp,
279}
280
281impl ValidationContext {
282 #[must_use]
284 pub fn fork_is_active(self) -> bool {
285 self.fork.is_active_at(self.block_number, self.timestamp)
286 }
287}
288
289#[cfg(test)]
290#[path = "fork_tests.rs"]
291mod tests;