1use crate::{
2 bootnodes::andantino_nodes,
3 hardfork::{TempoHardfork, TempoHardforks},
4};
5use alloy_eips::eip7840::BlobParams;
6use alloy_genesis::Genesis;
7use alloy_primitives::{Address, B256, U256};
8use reth_chainspec::{
9 BaseFeeParams, Chain, ChainSpec, DepositContract, DisplayHardforks, EthChainSpec,
10 EthereumHardfork, EthereumHardforks, ForkCondition, ForkFilter, ForkId, Hardfork, Hardforks,
11 Head,
12};
13use reth_cli::chainspec::{ChainSpecParser, parse_genesis};
14use reth_ethereum::evm::primitives::eth::spec::EthExecutorSpec;
15use reth_network_peers::NodeRecord;
16use std::sync::{Arc, LazyLock};
17use tempo_commonware_node_config::{Peers, PublicPolynomial};
18use tempo_primitives::TempoHeader;
19
20pub const TEMPO_BASE_FEE: u64 = 10_000_000_000;
21
22#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
24#[serde(rename_all = "camelCase")]
25pub struct TempoGenesisInfo {
26 #[serde(skip_serializing_if = "Option::is_none")]
28 adagio_time: Option<u64>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 moderato_time: Option<u64>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 allegretto_time: Option<u64>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 allegro_moderato_time: Option<u64>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 epoch_length: Option<u64>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 public_polynomial: Option<PublicPolynomial>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 validators: Option<Peers>,
53}
54
55impl TempoGenesisInfo {
56 fn extract_from(genesis: &Genesis) -> Self {
58 genesis
59 .config
60 .extra_fields
61 .deserialize_as::<Self>()
62 .unwrap_or_default()
63 }
64
65 pub fn epoch_length(&self) -> Option<u64> {
66 self.epoch_length
67 }
68
69 pub fn public_polynomial(&self) -> &Option<PublicPolynomial> {
70 &self.public_polynomial
71 }
72
73 pub fn validators(&self) -> &Option<Peers> {
74 &self.validators
75 }
76}
77
78#[derive(Debug, Clone, Default)]
80pub struct TempoChainSpecParser;
81
82pub const SUPPORTED_CHAINS: &[&str] = &["testnet"];
84
85pub fn chain_value_parser(s: &str) -> eyre::Result<Arc<TempoChainSpec>> {
90 Ok(match s {
91 "testnet" => ANDANTINO.clone(),
92 "dev" => DEV.clone(),
93 _ => TempoChainSpec::from_genesis(parse_genesis(s)?).into(),
94 })
95}
96
97impl ChainSpecParser for TempoChainSpecParser {
98 type ChainSpec = TempoChainSpec;
99
100 const SUPPORTED_CHAINS: &'static [&'static str] = SUPPORTED_CHAINS;
101
102 fn parse(s: &str) -> eyre::Result<Arc<Self::ChainSpec>> {
103 chain_value_parser(s)
104 }
105}
106
107pub static ANDANTINO: LazyLock<Arc<TempoChainSpec>> = LazyLock::new(|| {
108 let genesis: Genesis = serde_json::from_str(include_str!("./genesis/andantino.json"))
109 .expect("`./genesis/andantino.json` must be present and deserializable");
110 TempoChainSpec::from_genesis(genesis).into()
111});
112
113pub static DEV: LazyLock<Arc<TempoChainSpec>> = LazyLock::new(|| {
117 let genesis: Genesis = serde_json::from_str(include_str!("./genesis/dev.json"))
118 .expect("`./genesis/dev.json` must be present and deserializable");
119 TempoChainSpec::from_genesis(genesis).into()
120});
121
122#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct TempoChainSpec {
125 pub inner: ChainSpec<TempoHeader>,
127 pub info: TempoGenesisInfo,
128}
129
130impl TempoChainSpec {
131 pub fn from_genesis(genesis: Genesis) -> Self {
133 let info @ TempoGenesisInfo {
135 adagio_time,
136 moderato_time,
137 allegretto_time,
138 allegro_moderato_time,
139 ..
140 } = TempoGenesisInfo::extract_from(&genesis);
141
142 let mut base_spec = ChainSpec::from_genesis(genesis);
144
145 let tempo_forks = vec![
146 (TempoHardfork::Adagio, adagio_time),
147 (TempoHardfork::Moderato, moderato_time),
148 (TempoHardfork::Allegretto, allegretto_time),
149 (TempoHardfork::AllegroModerato, allegro_moderato_time),
150 ]
151 .into_iter()
152 .filter_map(|(fork, time)| time.map(|time| (fork, ForkCondition::Timestamp(time))));
153
154 base_spec.hardforks.extend(tempo_forks);
155
156 Self {
157 inner: base_spec.map_header(|inner| TempoHeader {
158 general_gas_limit: 0,
159 timestamp_millis_part: inner.timestamp * 1000,
160 shared_gas_limit: 0,
161 inner,
162 }),
163 info,
164 }
165 }
166}
167
168impl From<ChainSpec> for TempoChainSpec {
171 fn from(spec: ChainSpec) -> Self {
172 Self {
173 inner: spec.map_header(|inner| TempoHeader {
174 general_gas_limit: 0,
175 timestamp_millis_part: inner.timestamp * 1000,
176 inner,
177 shared_gas_limit: 0,
178 }),
179 info: TempoGenesisInfo::default(),
180 }
181 }
182}
183
184impl Hardforks for TempoChainSpec {
185 fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition {
186 self.inner.fork(fork)
187 }
188
189 fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> {
190 self.inner.forks_iter()
191 }
192
193 fn fork_id(&self, head: &Head) -> ForkId {
194 self.inner.fork_id(head)
195 }
196
197 fn latest_fork_id(&self) -> ForkId {
198 self.inner.latest_fork_id()
199 }
200
201 fn fork_filter(&self, head: Head) -> ForkFilter {
202 self.inner.fork_filter(head)
203 }
204}
205
206impl EthChainSpec for TempoChainSpec {
207 type Header = TempoHeader;
208
209 fn chain(&self) -> Chain {
210 self.inner.chain()
211 }
212
213 fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams {
214 self.inner.base_fee_params_at_timestamp(timestamp)
215 }
216
217 fn blob_params_at_timestamp(&self, timestamp: u64) -> Option<BlobParams> {
218 self.inner.blob_params_at_timestamp(timestamp)
219 }
220
221 fn deposit_contract(&self) -> Option<&DepositContract> {
222 self.inner.deposit_contract()
223 }
224
225 fn genesis_hash(&self) -> B256 {
226 self.inner.genesis_hash()
227 }
228
229 fn prune_delete_limit(&self) -> usize {
230 self.inner.prune_delete_limit()
231 }
232
233 fn display_hardforks(&self) -> Box<dyn std::fmt::Display> {
234 let tempo_forks = self.inner.hardforks.forks_iter().filter(|(fork, _)| {
236 !EthereumHardfork::VARIANTS
237 .iter()
238 .any(|h| h.name() == (*fork).name())
239 });
240
241 Box::new(DisplayHardforks::new(tempo_forks))
242 }
243
244 fn genesis_header(&self) -> &Self::Header {
245 self.inner.genesis_header()
246 }
247
248 fn genesis(&self) -> &Genesis {
249 self.inner.genesis()
250 }
251
252 fn bootnodes(&self) -> Option<Vec<NodeRecord>> {
253 match self.inner.chain_id() {
254 42429 => Some(andantino_nodes()),
255 _ => self.inner.bootnodes(),
256 }
257 }
258
259 fn final_paris_total_difficulty(&self) -> Option<U256> {
260 self.inner.get_final_paris_total_difficulty()
261 }
262
263 fn next_block_base_fee(&self, _parent: &TempoHeader, _target_timestamp: u64) -> Option<u64> {
264 Some(TEMPO_BASE_FEE)
265 }
266}
267
268impl EthereumHardforks for TempoChainSpec {
269 fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition {
270 self.inner.ethereum_fork_activation(fork)
271 }
272}
273
274impl EthExecutorSpec for TempoChainSpec {
275 fn deposit_contract_address(&self) -> Option<Address> {
276 self.inner.deposit_contract_address()
277 }
278}
279
280impl TempoHardforks for TempoChainSpec {
281 fn tempo_fork_activation(&self, fork: TempoHardfork) -> ForkCondition {
282 self.fork(fork)
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use crate::hardfork::{TempoHardfork, TempoHardforks};
289 use reth_chainspec::{EthereumHardfork, ForkCondition, Hardforks};
290 use reth_cli::chainspec::ChainSpecParser as _;
291 use serde_json::json;
292
293 #[test]
294 fn can_load_testnet() {
295 let _ = super::TempoChainSpecParser::parse("testnet")
296 .expect("the testnet chainspec must always be well formed");
297 }
298
299 #[test]
300 fn can_load_dev() {
301 let _ = super::TempoChainSpecParser::parse("dev")
302 .expect("the dev chainspec must always be well formed");
303 }
304
305 #[test]
306 fn test_tempo_chainspec_has_tempo_hardforks() {
307 let chainspec = super::TempoChainSpecParser::parse("testnet")
308 .expect("the testnet chainspec must always be well formed");
309
310 assert!(chainspec.is_adagio_active_at_timestamp(0));
312 }
313
314 #[test]
315 fn test_tempo_chainspec_implements_tempo_hardforks_trait() {
316 let chainspec = super::TempoChainSpecParser::parse("testnet")
317 .expect("the testnet chainspec must always be well formed");
318
319 let activation = chainspec.tempo_fork_activation(TempoHardfork::Adagio);
321 assert_eq!(activation, ForkCondition::Timestamp(0));
322
323 assert!(chainspec.is_adagio_active_at_timestamp(0));
325 assert!(chainspec.is_adagio_active_at_timestamp(1000));
326 }
327
328 #[test]
329 fn test_tempo_hardforks_in_inner_hardforks() {
330 let chainspec = super::TempoChainSpecParser::parse("testnet")
331 .expect("the testnet chainspec must always be well formed");
332
333 let activation = chainspec.fork(TempoHardfork::Adagio);
335 assert_eq!(activation, ForkCondition::Timestamp(0));
336
337 let has_adagio = chainspec
339 .forks_iter()
340 .any(|(fork, _)| fork.name() == "Adagio");
341 assert!(has_adagio, "Adagio hardfork should be in inner.hardforks");
342 }
343
344 #[test]
345 fn test_parse_tempo_hardforks_from_genesis_extra_fields() {
346 let genesis_json = json!({
349 "config": {
350 "chainId": 1337,
351 "homesteadBlock": 0,
352 "eip150Block": 0,
353 "eip155Block": 0,
354 "eip158Block": 0,
355 "byzantiumBlock": 0,
356 "constantinopleBlock": 0,
357 "petersburgBlock": 0,
358 "istanbulBlock": 0,
359 "berlinBlock": 0,
360 "londonBlock": 0,
361 "mergeNetsplitBlock": 0,
362 "terminalTotalDifficulty": 0,
363 "terminalTotalDifficultyPassed": true,
364 "shanghaiTime": 0,
365 "cancunTime": 0,
366 "adagioTime": 1000,
367 "moderatoTime": 2000,
368 "allegrettoTime": 3000,
369 "allegroModeratoTime": 4000,
370 },
371 "alloc": {}
372 });
373
374 let genesis: alloy_genesis::Genesis =
375 serde_json::from_value(genesis_json).expect("genesis should be valid");
376
377 let chainspec = super::TempoChainSpec::from_genesis(genesis);
378
379 let activation = chainspec.fork(TempoHardfork::Adagio);
381 assert_eq!(
382 activation,
383 ForkCondition::Timestamp(1000),
384 "Adagio should be activated at the parsed timestamp from extra_fields"
385 );
386
387 assert!(
388 !chainspec.is_adagio_active_at_timestamp(0),
389 "Adagio should not be active before its activation timestamp"
390 );
391 assert!(
392 chainspec.is_adagio_active_at_timestamp(1000),
393 "Adagio should be active at its activation timestamp"
394 );
395 assert!(
396 chainspec.is_adagio_active_at_timestamp(2000),
397 "Adagio should be active after its activation timestamp"
398 );
399
400 let activation = chainspec.fork(TempoHardfork::Moderato);
402 assert_eq!(
403 activation,
404 ForkCondition::Timestamp(2000),
405 "Moderato should be activated at the parsed timestamp from extra_fields"
406 );
407
408 assert!(
409 !chainspec.is_moderato_active_at_timestamp(0),
410 "Moderato should not be active before its activation timestamp"
411 );
412 assert!(
413 !chainspec.is_moderato_active_at_timestamp(1000),
414 "Moderato should not be active at Adagio's activation timestamp"
415 );
416 assert!(
417 chainspec.is_moderato_active_at_timestamp(2000),
418 "Moderato should be active at its activation timestamp"
419 );
420 assert!(
421 chainspec.is_moderato_active_at_timestamp(3000),
422 "Moderato should be active after its activation timestamp"
423 );
424
425 let activation = chainspec.fork(TempoHardfork::Allegretto);
427 assert_eq!(
428 activation,
429 ForkCondition::Timestamp(3000),
430 "Allegretto should be activated at the parsed timestamp from extra_fields"
431 );
432
433 assert!(
434 !chainspec.is_allegretto_active_at_timestamp(0),
435 "Allegretto should not be active before its activation timestamp"
436 );
437 assert!(
438 !chainspec.is_allegretto_active_at_timestamp(1000),
439 "Allegretto should not be active at Adagio's activation timestamp"
440 );
441 assert!(
442 !chainspec.is_allegretto_active_at_timestamp(2000),
443 "Allegretto should not be active at Moderato's activation timestamp"
444 );
445 assert!(
446 chainspec.is_allegretto_active_at_timestamp(3000),
447 "Allegretto should be active at its activation timestamp"
448 );
449 assert!(
450 chainspec.is_allegretto_active_at_timestamp(4000),
451 "Allegretto should be active after its activation timestamp"
452 );
453
454 let activation = chainspec.fork(TempoHardfork::AllegroModerato);
456 assert_eq!(
457 activation,
458 ForkCondition::Timestamp(4000),
459 "AllegroModerato should be activated at the parsed timestamp from extra_fields"
460 );
461
462 assert!(
463 !chainspec.is_allegro_moderato_active_at_timestamp(0),
464 "AllegroModerato should not be active before its activation timestamp"
465 );
466 assert!(
467 !chainspec.is_allegro_moderato_active_at_timestamp(1000),
468 "AllegroModerato should not be active at Adagio's activation timestamp"
469 );
470 assert!(
471 !chainspec.is_allegro_moderato_active_at_timestamp(2000),
472 "AllegroModerato should not be active at Moderato's activation timestamp"
473 );
474 assert!(
475 !chainspec.is_allegro_moderato_active_at_timestamp(3000),
476 "AllegroModerato should not be active at Allegretto's activation timestamp"
477 );
478 assert!(
479 chainspec.is_allegro_moderato_active_at_timestamp(4000),
480 "AllegroModerato should be active at its activation timestamp"
481 );
482 assert!(
483 chainspec.is_allegro_moderato_active_at_timestamp(5000),
484 "AllegroModerato should be active after its activation timestamp"
485 );
486 }
487
488 #[test]
489 fn test_tempo_hardforks_are_ordered_correctly() {
490 let genesis_json = json!({
492 "config": {
493 "chainId": 1337,
494 "homesteadBlock": 0,
495 "eip150Block": 0,
496 "eip155Block": 0,
497 "eip158Block": 0,
498 "byzantiumBlock": 0,
499 "constantinopleBlock": 0,
500 "petersburgBlock": 0,
501 "istanbulBlock": 0,
502 "berlinBlock": 0,
503 "londonBlock": 0,
504 "mergeNetsplitBlock": 0,
505 "terminalTotalDifficulty": 0,
506 "terminalTotalDifficultyPassed": true,
507 "shanghaiTime": 0,
508 "cancunTime": 2000,
509 "adagioTime": 1000,
510 },
511 "alloc": {}
512 });
513
514 let genesis: alloy_genesis::Genesis =
515 serde_json::from_value(genesis_json).expect("genesis should be valid");
516
517 let chainspec = super::TempoChainSpec::from_genesis(genesis);
518
519 let forks: Vec<_> = chainspec.inner.hardforks.forks_iter().collect();
521
522 let shanghai_pos = forks
524 .iter()
525 .position(|(f, _)| f.name() == EthereumHardfork::Shanghai.name());
526 let adagio_pos = forks
527 .iter()
528 .position(|(f, _)| f.name() == TempoHardfork::Adagio.name());
529 let cancun_pos = forks
530 .iter()
531 .position(|(f, _)| f.name() == EthereumHardfork::Cancun.name());
532
533 assert!(shanghai_pos.is_some(), "Shanghai should be present");
534 assert!(adagio_pos.is_some(), "Adagio should be present");
535 assert!(cancun_pos.is_some(), "Cancun should be present");
536
537 assert!(
539 shanghai_pos.unwrap() < adagio_pos.unwrap(),
540 "Shanghai (time 0) should come before Adagio (time 1000), but got positions {} and {}",
541 shanghai_pos.unwrap(),
542 adagio_pos.unwrap()
543 );
544 assert!(
545 adagio_pos.unwrap() < cancun_pos.unwrap(),
546 "Adagio (time 1000) should come before Cancun (time 2000), but got positions {} and {}",
547 adagio_pos.unwrap(),
548 cancun_pos.unwrap()
549 );
550 }
551
552 #[test]
553 fn test_tempo_hardfork_at() {
554 let genesis_json = json!({
556 "config": {
557 "chainId": 1337,
558 "homesteadBlock": 0,
559 "eip150Block": 0,
560 "eip155Block": 0,
561 "eip158Block": 0,
562 "byzantiumBlock": 0,
563 "constantinopleBlock": 0,
564 "petersburgBlock": 0,
565 "istanbulBlock": 0,
566 "berlinBlock": 0,
567 "londonBlock": 0,
568 "mergeNetsplitBlock": 0,
569 "terminalTotalDifficulty": 0,
570 "terminalTotalDifficultyPassed": true,
571 "shanghaiTime": 0,
572 "cancunTime": 0,
573 "adagioTime": 1000,
574 "moderatoTime": 2000,
575 "allegrettoTime": 3000,
576 "allegroModeratoTime": 4000
577 },
578 "alloc": {}
579 });
580
581 let genesis: alloy_genesis::Genesis =
582 serde_json::from_value(genesis_json).expect("genesis should be valid");
583
584 let chainspec = super::TempoChainSpec::from_genesis(genesis);
585
586 assert_eq!(
588 chainspec.tempo_hardfork_at(0),
589 TempoHardfork::Adagio,
590 "Should return Adagio at timestamp 0"
591 );
592
593 assert_eq!(
595 chainspec.tempo_hardfork_at(1000),
596 TempoHardfork::Adagio,
597 "Should return Adagio at its activation time"
598 );
599
600 assert_eq!(
602 chainspec.tempo_hardfork_at(1500),
603 TempoHardfork::Adagio,
604 "Should return Adagio between Adagio and Moderato activation"
605 );
606
607 assert_eq!(
609 chainspec.tempo_hardfork_at(2000),
610 TempoHardfork::Moderato,
611 "Should return Moderato at its activation time"
612 );
613
614 assert_eq!(
616 chainspec.tempo_hardfork_at(2500),
617 TempoHardfork::Moderato,
618 "Should return Moderato between Moderato and Allegretto activation"
619 );
620
621 assert_eq!(
623 chainspec.tempo_hardfork_at(3000),
624 TempoHardfork::Allegretto,
625 "Should return Allegretto at its activation time"
626 );
627
628 assert_eq!(
630 chainspec.tempo_hardfork_at(3500),
631 TempoHardfork::Allegretto,
632 "Should return Allegretto between Allegretto and AllegroModerato activation"
633 );
634
635 assert_eq!(
637 chainspec.tempo_hardfork_at(4000),
638 TempoHardfork::AllegroModerato,
639 "Should return AllegroModerato at its activation time"
640 );
641
642 assert_eq!(
644 chainspec.tempo_hardfork_at(5000),
645 TempoHardfork::AllegroModerato,
646 "Should return AllegroModerato after its activation time"
647 );
648 }
649}