1use alloy_primitives::{BlockNumber, BlockTimestamp, ChainId, B256};
17use anyhow::bail;
18use serde::{Deserialize, Serialize};
19use sha2::{digest::Output, Digest, Sha256};
20use std::collections::BTreeMap;
21
22#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
24pub enum ForkCondition {
25 Block(BlockNumber),
27 Timestamp(BlockTimestamp),
29}
30
31impl ForkCondition {
32 #[inline]
34 pub fn active(&self, block_number: BlockNumber, timestamp: u64) -> bool {
35 match self {
36 ForkCondition::Block(block) => *block <= block_number,
37 ForkCondition::Timestamp(ts) => *ts <= timestamp,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
44pub struct ChainSpec<S: Ord> {
45 pub chain_id: ChainId,
47 pub forks: BTreeMap<S, ForkCondition>,
49}
50
51impl<S: Ord + Serialize> ChainSpec<S> {
52 pub fn new_single(chain_id: ChainId, spec_id: S) -> Self {
61 ChainSpec {
62 chain_id,
63 forks: BTreeMap::from([(spec_id, ForkCondition::Block(0))]),
64 }
65 }
66
67 #[inline]
69 pub fn chain_id(&self) -> ChainId {
70 self.chain_id
71 }
72
73 #[inline]
75 pub fn digest(&self) -> B256 {
76 <[u8; 32]>::from(StructHash::digest::<Sha256>(self)).into()
77 }
78
79 pub fn active_fork(&self, block_number: BlockNumber, timestamp: u64) -> anyhow::Result<&S> {
81 for (spec_id, fork) in self.forks.iter().rev() {
82 if fork.active(block_number, timestamp) {
83 return Ok(spec_id);
84 }
85 }
86 bail!("no supported fork for block {}", block_number)
87 }
88}
89
90trait StructHash {
94 fn digest<D: Digest>(&self) -> Output<D>;
95}
96
97impl<S: Serialize> StructHash for (&S, &ForkCondition) {
98 fn digest<D: Digest>(&self) -> Output<D> {
101 let mut hasher = D::new();
102 let s_bytes = bincode::serialize(&self.0).unwrap();
104 hasher.update(&s_bytes);
105 match self.1 {
106 ForkCondition::Block(n) => {
107 hasher.update(b"Block");
108 hasher.update(n.to_le_bytes());
109 }
110 ForkCondition::Timestamp(ts) => {
111 hasher.update(b"Timestamp");
112 hasher.update(ts.to_le_bytes());
113 }
114 }
115 hasher.finalize()
116 }
117}
118
119impl<S: Ord + Serialize> StructHash for ChainSpec<S> {
120 fn digest<D: Digest>(&self) -> Output<D> {
126 let tag_digest = D::digest(b"ChainSpec(chain_id,forks)");
127
128 let mut hasher = D::new();
129 hasher.update(tag_digest);
130 self.forks
132 .iter()
133 .for_each(|fork| hasher.update(fork.digest::<D>()));
134 hasher.update(self.chain_id.to_le_bytes());
136 hasher.update(u16::try_from(self.forks.len()).unwrap().to_le_bytes());
138
139 hasher.finalize()
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use revm::primitives::hardfork::SpecId;
147
148 #[test]
149 fn active_fork() {
150 let spec = ChainSpec {
151 chain_id: 1,
152 forks: BTreeMap::from([
153 (SpecId::MERGE, ForkCondition::Block(2)),
154 (SpecId::CANCUN, ForkCondition::Timestamp(60)),
155 ]),
156 };
157
158 assert!(spec.active_fork(0, 0).is_err());
159 assert_eq!(*spec.active_fork(2, 0).unwrap(), SpecId::MERGE);
160 assert_eq!(*spec.active_fork(u64::MAX, 59).unwrap(), SpecId::MERGE);
161 assert_eq!(*spec.active_fork(0, 60).unwrap(), SpecId::CANCUN);
162 assert_eq!(
163 *spec.active_fork(u64::MAX, u64::MAX).unwrap(),
164 SpecId::CANCUN
165 );
166 }
167
168 #[test]
169 fn digest() {
170 let chain_id = 0xF1E2D3C4B5A69788;
171 let forks = [
172 (SpecId::FRONTIER, ForkCondition::Block(0xF1E2D3C4B5A69788)),
173 (SpecId::PRAGUE, ForkCondition::Timestamp(0xF1E2D3C4B5A69788)),
174 ];
175 let chain_spec = ChainSpec {
176 chain_id,
177 forks: BTreeMap::from(forks.clone()),
178 };
179
180 let exp: [u8; 32] = {
181 let mut h = Sha256::new();
182 h.update(Sha256::digest(b"ChainSpec(chain_id,forks)"));
184 let fork_digest = {
186 let (spec, ForkCondition::Block(n)) = forks[0] else {
187 unreachable!()
188 };
189 let mut h = Sha256::new();
190 h.update((spec as u32).to_le_bytes());
191 h.update(b"Block");
192 h.update(n.to_le_bytes());
193 h.finalize()
194 };
195 h.update(fork_digest);
196 let fork_digest = {
197 let (spec, ForkCondition::Timestamp(ts)) = forks[1] else {
198 unreachable!()
199 };
200 let mut h = Sha256::new();
201 h.update((spec as u32).to_le_bytes());
202 h.update(b"Timestamp");
203 h.update(ts.to_le_bytes());
204 h.finalize()
205 };
206 h.update(fork_digest);
207 h.update((chain_id as u32).to_le_bytes());
209 h.update(((chain_id >> 32) as u32).to_le_bytes());
210 h.update(2u16.to_le_bytes());
212
213 h.finalize().into()
214 };
215
216 assert_eq!(chain_spec.digest(), B256::from(exp));
217 }
218}