Skip to main content

tempo_node/rpc/
fork_schedule.rs

1use jsonrpsee::{core::RpcResult, proc_macros::rpc};
2use reth_chainspec::{EthereumHardfork, ForkCondition, Hardforks, Head};
3use reth_primitives_traits::AlloyBlockHeader as _;
4use reth_provider::{BlockNumReader, ChainSpecProvider, HeaderProvider};
5use serde::{Deserialize, Serialize};
6use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardforks};
7
8/// Response for `tempo_forkSchedule`.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct ForkSchedule {
12    /// Ordered list of Tempo-specific forks (excludes Genesis and Ethereum forks).
13    pub schedule: Vec<ForkInfo>,
14    /// Name of the latest active Tempo fork at the chain head.
15    pub active: String,
16}
17
18/// Information about a single Tempo fork.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct ForkInfo {
22    /// Fork name (e.g. "T0", "T1", "T2").
23    pub name: String,
24    /// Activation timestamp.
25    pub activation_time: u64,
26    /// Whether this fork is active at the chain head.
27    pub active: bool,
28    /// EIP-2124 fork hash at this fork's activation point (e.g. `"0x471a451c"`).
29    /// `None` if the fork is not yet active.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub fork_id: Option<String>,
32}
33
34#[rpc(server, namespace = "tempo")]
35pub trait TempoForkScheduleApi {
36    /// Returns the Tempo fork schedule and the currently active fork.
37    #[method(name = "forkSchedule")]
38    async fn fork_schedule(&self) -> RpcResult<ForkSchedule>;
39}
40
41/// Implementation of `tempo_forkSchedule`.
42#[derive(Debug, Clone)]
43pub struct TempoForkScheduleRpc<P> {
44    provider: P,
45}
46
47impl<P> TempoForkScheduleRpc<P> {
48    /// Create a new fork schedule RPC handler.
49    pub fn new(provider: P) -> Self {
50        Self { provider }
51    }
52}
53
54fn internal_err(msg: impl ToString) -> jsonrpsee::types::ErrorObject<'static> {
55    jsonrpsee::types::ErrorObject::owned(-32000, msg.to_string(), None::<()>)
56}
57
58#[async_trait::async_trait]
59impl<P> TempoForkScheduleApiServer for TempoForkScheduleRpc<P>
60where
61    P: ChainSpecProvider<ChainSpec = TempoChainSpec>
62        + BlockNumReader
63        + HeaderProvider
64        + Send
65        + Sync
66        + 'static,
67{
68    async fn fork_schedule(&self) -> RpcResult<ForkSchedule> {
69        let chain_spec = self.provider.chain_spec();
70
71        let best_number = self.provider.best_block_number().map_err(internal_err)?;
72        let header = self
73            .provider
74            .header_by_number(best_number)
75            .map_err(internal_err)?
76            .ok_or_else(|| internal_err("head header not found"))?;
77        let head_timestamp = header.timestamp();
78
79        // Only Tempo forks (exclude Ethereum hardforks and Genesis).
80        let schedule = chain_spec
81            .forks_iter()
82            .filter(|(fork, _)| {
83                let name = fork.name();
84                name != "Genesis" && !EthereumHardfork::VARIANTS.iter().any(|h| h.name() == name)
85            })
86            .filter_map(|(fork, condition)| {
87                let ForkCondition::Timestamp(ts) = condition else {
88                    return None;
89                };
90                let active = ts <= head_timestamp;
91                let fork_id = active.then(|| {
92                    let id = chain_spec.fork_id(&Head {
93                        number: best_number,
94                        timestamp: ts,
95                        ..Default::default()
96                    });
97                    format!(
98                        "0x{}",
99                        id.hash
100                            .0
101                            .iter()
102                            .map(|b| format!("{b:02x}"))
103                            .collect::<String>()
104                    )
105                });
106                Some(ForkInfo {
107                    name: fork.name().to_string(),
108                    activation_time: ts,
109                    active,
110                    fork_id,
111                })
112            })
113            .collect();
114
115        let active = chain_spec
116            .tempo_hardfork_at(head_timestamp)
117            .name()
118            .to_string();
119
120        Ok(ForkSchedule { schedule, active })
121    }
122}