1#![deny(missing_docs)]
2#![allow(clippy::redundant_else, clippy::too_many_lines)]
3#![doc = include_str!("../README.md")]
4
5use crc::crc32;
6use maplit::btreemap;
7use primitive_types::H256;
8use std::{
9 collections::{BTreeMap, BTreeSet},
10 ops::{Add, AddAssign},
11};
12use thiserror::Error;
13
14pub type BlockNumber = u64;
16
17#[derive(
19 Clone,
20 Copy,
21 Debug,
22 PartialEq,
23 Eq,
24 Hash,
25 fastrlp::EncodableWrapper,
26 fastrlp::DecodableWrapper,
27 fastrlp::MaxEncodedLen,
28)]
29pub struct ForkHash(pub [u8; 4]);
30
31impl From<H256> for ForkHash {
32 fn from(genesis: H256) -> Self {
33 Self(crc32::checksum_ieee(&genesis[..]).to_be_bytes())
34 }
35}
36
37impl AddAssign<BlockNumber> for ForkHash {
38 fn add_assign(&mut self, block: BlockNumber) {
39 let blob = block.to_be_bytes();
40 self.0 = crc32::update(u32::from_be_bytes(self.0), &crc32::IEEE_TABLE, &blob).to_be_bytes();
41 }
42}
43
44impl Add<BlockNumber> for ForkHash {
45 type Output = Self;
46 fn add(mut self, block: BlockNumber) -> Self {
47 self += block;
48 self
49 }
50}
51
52#[derive(
55 Clone,
56 Copy,
57 Debug,
58 PartialEq,
59 Eq,
60 Hash,
61 fastrlp::Encodable,
62 fastrlp::Decodable,
63 fastrlp::MaxEncodedLen,
64)]
65pub struct ForkId {
66 pub hash: ForkHash,
68 pub next: BlockNumber,
70}
71
72#[derive(Clone, Copy, Debug, Error, PartialEq, Eq, Hash)]
74pub enum ValidationError {
75 #[error("remote node is outdated and needs a software update")]
77 RemoteStale,
78 #[error("local node is on an incompatible chain or needs a software update")]
80 LocalIncompatibleOrStale,
81}
82
83#[derive(Clone, Debug, PartialEq)]
85pub struct ForkFilter {
86 forks: BTreeMap<BlockNumber, ForkHash>,
87
88 head: BlockNumber,
89
90 cache: Cache,
91}
92
93#[derive(Clone, Debug, PartialEq)]
94struct Cache {
95 epoch_start: BlockNumber,
98 epoch_end: Option<BlockNumber>,
99 past: Vec<(BlockNumber, ForkHash)>,
100 future: Vec<ForkHash>,
101 fork_id: ForkId,
102}
103
104impl Cache {
105 fn compute_cache(forks: &BTreeMap<BlockNumber, ForkHash>, head: BlockNumber) -> Self {
106 let mut past = Vec::with_capacity(forks.len());
107 let mut future = Vec::with_capacity(forks.len());
108
109 let mut epoch_start = 0;
110 let mut epoch_end = None;
111 for (block, hash) in forks {
112 if *block <= head {
113 epoch_start = *block;
114 past.push((*block, *hash));
115 } else {
116 if epoch_end.is_none() {
117 epoch_end = Some(*block);
118 }
119 future.push(*hash);
120 }
121 }
122
123 let fork_id = ForkId {
124 hash: past
125 .last()
126 .expect("there is always at least one - genesis - fork hash; qed")
127 .1,
128 next: epoch_end.unwrap_or(0),
129 };
130
131 Self {
132 epoch_start,
133 epoch_end,
134 past,
135 future,
136 fork_id,
137 }
138 }
139}
140
141impl ForkFilter {
142 pub fn new<F>(head: BlockNumber, genesis: H256, forks: F) -> Self
144 where
145 F: IntoIterator<Item = BlockNumber>,
146 {
147 let genesis_fork_hash = ForkHash::from(genesis);
148 let mut forks = forks.into_iter().collect::<BTreeSet<_>>();
149 forks.remove(&0);
150 let forks = forks
151 .into_iter()
152 .fold(
153 (btreemap! { 0 => genesis_fork_hash }, genesis_fork_hash),
154 |(mut acc, base_hash), block| {
155 let fork_hash = base_hash + block;
156 acc.insert(block, fork_hash);
157 (acc, fork_hash)
158 },
159 )
160 .0;
161
162 let cache = Cache::compute_cache(&forks, head);
163
164 Self { forks, head, cache }
165 }
166
167 fn set_head_priv(&mut self, head: BlockNumber) -> bool {
168 #[allow(clippy::option_if_let_else)]
169 let recompute_cache = {
170 if head < self.cache.epoch_start {
171 true
172 } else if let Some(epoch_end) = self.cache.epoch_end {
173 head >= epoch_end
174 } else {
175 false
176 }
177 };
178
179 if recompute_cache {
180 self.cache = Cache::compute_cache(&self.forks, head);
181 }
182 self.head = head;
183
184 recompute_cache
185 }
186
187 pub fn set_head(&mut self, head: BlockNumber) {
189 self.set_head_priv(head);
190 }
191
192 #[must_use]
194 pub const fn current(&self) -> ForkId {
195 self.cache.fork_id
196 }
197
198 pub fn validate(&self, fork_id: ForkId) -> Result<(), ValidationError> {
203 if self.current().hash == fork_id.hash {
205 if fork_id.next == 0 {
206 return Ok(());
208 }
209
210 if self.head >= fork_id.next {
212 return Err(ValidationError::LocalIncompatibleOrStale);
215 } else {
216 return Ok(());
218 }
219 }
220
221 let mut it = self.cache.past.iter();
223 while let Some((_, hash)) = it.next() {
224 if *hash == fork_id.hash {
225 if let Some((actual_fork_block, _)) = it.next() {
227 if *actual_fork_block == fork_id.next {
228 return Ok(());
229 } else {
230 return Err(ValidationError::RemoteStale);
231 }
232 }
233
234 break;
235 }
236 }
237
238 for future_fork_hash in &self.cache.future {
240 if *future_fork_hash == fork_id.hash {
241 return Ok(());
242 }
243 }
244
245 Err(ValidationError::LocalIncompatibleOrStale)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use hex_literal::hex;
254
255 const GENESIS_HASH: H256 = H256(hex!(
256 "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
257 ));
258
259 #[test]
262 fn forkhash() {
263 let mut fork_hash = ForkHash::from(GENESIS_HASH);
264 assert_eq!(fork_hash.0, hex!("fc64ec04"));
265
266 fork_hash += 1_150_000;
267 assert_eq!(fork_hash.0, hex!("97c2c34c"));
268
269 fork_hash += 1_920_000;
270 assert_eq!(fork_hash.0, hex!("91d1f948"));
271 }
272
273 #[test]
274 fn compatibility_check() {
275 let mut filter = ForkFilter::new(
276 0,
277 GENESIS_HASH,
278 vec![
279 1_150_000, 1_920_000, 2_463_000, 2_675_000, 4_370_000, 7_280_000,
280 ],
281 );
282
283 filter.set_head(7_987_396);
285 assert_eq!(
286 filter.validate(ForkId {
287 hash: ForkHash(hex!("668db0af")),
288 next: 0
289 }),
290 Ok(())
291 );
292
293 filter.set_head(7_987_396);
296 assert_eq!(
297 filter.validate(ForkId {
298 hash: ForkHash(hex!("668db0af")),
299 next: BlockNumber::max_value()
300 }),
301 Ok(())
302 );
303
304 filter.set_head(7_279_999);
308 assert_eq!(
309 filter.validate(ForkId {
310 hash: ForkHash(hex!("a00bc324")),
311 next: 0
312 }),
313 Ok(())
314 );
315
316 filter.set_head(7_279_999);
320 assert_eq!(
321 filter.validate(ForkId {
322 hash: ForkHash(hex!("a00bc324")),
323 next: 7_280_000
324 }),
325 Ok(())
326 );
327
328 filter.set_head(7_279_999);
332 assert_eq!(
333 filter.validate(ForkId {
334 hash: ForkHash(hex!("a00bc324")),
335 next: BlockNumber::max_value()
336 }),
337 Ok(())
338 );
339
340 filter.set_head(7_987_396);
342 assert_eq!(
343 filter.validate(ForkId {
344 hash: ForkHash(hex!("a00bc324")),
345 next: 7_280_000
346 }),
347 Ok(())
348 );
349
350 filter.set_head(7_987_396);
353 assert_eq!(
354 filter.validate(ForkId {
355 hash: ForkHash(hex!("3edd5b10")),
356 next: 4_370_000
357 }),
358 Ok(())
359 );
360
361 filter.set_head(7_279_999);
363 assert_eq!(
364 filter.validate(ForkId {
365 hash: ForkHash(hex!("668db0af")),
366 next: 0
367 }),
368 Ok(())
369 );
370
371 filter.set_head(4_369_999);
374 assert_eq!(
375 filter.validate(ForkId {
376 hash: ForkHash(hex!("a00bc324")),
377 next: 0
378 }),
379 Ok(())
380 );
381
382 filter.set_head(7_987_396);
385 assert_eq!(
386 filter.validate(ForkId {
387 hash: ForkHash(hex!("a00bc324")),
388 next: 0
389 }),
390 Err(ValidationError::RemoteStale)
391 );
392
393 filter.set_head(7_987_396);
396 assert_eq!(
397 filter.validate(ForkId {
398 hash: ForkHash(hex!("5cddc0e1")),
399 next: 0
400 }),
401 Err(ValidationError::LocalIncompatibleOrStale)
402 );
403
404 filter.set_head(7_279_999);
407 assert_eq!(
408 filter.validate(ForkId {
409 hash: ForkHash(hex!("5cddc0e1")),
410 next: 0
411 }),
412 Err(ValidationError::LocalIncompatibleOrStale)
413 );
414
415 filter.set_head(7_987_396);
417 assert_eq!(
418 filter.validate(ForkId {
419 hash: ForkHash(hex!("afec6b27")),
420 next: 0
421 }),
422 Err(ValidationError::LocalIncompatibleOrStale)
423 );
424
425 filter.set_head(88_888_888);
430 assert_eq!(
431 filter.validate(ForkId {
432 hash: ForkHash(hex!("668db0af")),
433 next: 88_888_888
434 }),
435 Err(ValidationError::LocalIncompatibleOrStale)
436 );
437
438 filter.set_head(7_279_999);
441 assert_eq!(
442 filter.validate(ForkId {
443 hash: ForkHash(hex!("a00bc324")),
444 next: 7_279_999
445 }),
446 Err(ValidationError::LocalIncompatibleOrStale)
447 );
448 }
449
450 #[test]
451 fn forkid_serialization() {
452 assert_eq!(
453 &*fastrlp::encode_fixed_size(&ForkId {
454 hash: ForkHash(hex!("00000000")),
455 next: 0
456 }),
457 hex!("c6840000000080")
458 );
459 assert_eq!(
460 &*fastrlp::encode_fixed_size(&ForkId {
461 hash: ForkHash(hex!("deadbeef")),
462 next: 0xBADD_CAFE
463 }),
464 hex!("ca84deadbeef84baddcafe")
465 );
466 assert_eq!(
467 &*fastrlp::encode_fixed_size(&ForkId {
468 hash: ForkHash(hex!("ffffffff")),
469 next: u64::max_value()
470 }),
471 hex!("ce84ffffffff88ffffffffffffffff")
472 );
473
474 assert_eq!(
475 <ForkId as fastrlp::Decodable>::decode(&mut (&hex!("c6840000000080") as &[u8]))
476 .unwrap(),
477 ForkId {
478 hash: ForkHash(hex!("00000000")),
479 next: 0
480 }
481 );
482 assert_eq!(
483 <ForkId as fastrlp::Decodable>::decode(&mut (&hex!("ca84deadbeef84baddcafe") as &[u8]))
484 .unwrap(),
485 ForkId {
486 hash: ForkHash(hex!("deadbeef")),
487 next: 0xBADD_CAFE
488 }
489 );
490 assert_eq!(
491 <ForkId as fastrlp::Decodable>::decode(
492 &mut (&hex!("ce84ffffffff88ffffffffffffffff") as &[u8])
493 )
494 .unwrap(),
495 ForkId {
496 hash: ForkHash(hex!("ffffffff")),
497 next: u64::max_value()
498 }
499 );
500 }
501
502 #[test]
503 fn compute_cache() {
504 let b1 = 1_150_000;
505 let b2 = 1_920_000;
506
507 let h0 = ForkId {
508 hash: ForkHash(hex!("fc64ec04")),
509 next: b1,
510 };
511 let h1 = ForkId {
512 hash: ForkHash(hex!("97c2c34c")),
513 next: b2,
514 };
515 let h2 = ForkId {
516 hash: ForkHash(hex!("91d1f948")),
517 next: 0,
518 };
519
520 let mut fork_filter = ForkFilter::new(0, GENESIS_HASH, vec![b1, b2]);
521
522 assert!(!fork_filter.set_head_priv(0));
523 assert_eq!(fork_filter.current(), h0);
524
525 assert!(!fork_filter.set_head_priv(1));
526 assert_eq!(fork_filter.current(), h0);
527
528 assert!(fork_filter.set_head_priv(b1 + 1));
529 assert_eq!(fork_filter.current(), h1);
530
531 assert!(!fork_filter.set_head_priv(b1));
532 assert_eq!(fork_filter.current(), h1);
533
534 assert!(fork_filter.set_head_priv(b1 - 1));
535 assert_eq!(fork_filter.current(), h0);
536
537 assert!(fork_filter.set_head_priv(b1));
538 assert_eq!(fork_filter.current(), h1);
539
540 assert!(!fork_filter.set_head_priv(b2 - 1));
541 assert_eq!(fork_filter.current(), h1);
542
543 assert!(fork_filter.set_head_priv(b2));
544 assert_eq!(fork_filter.current(), h2);
545 }
546}