Skip to main content

ethrex_rpc/types/
block_identifier.rs

1use std::{fmt::Display, str::FromStr};
2
3use ethrex_common::types::{BlockHash, BlockHeader, BlockNumber};
4use ethrex_storage::{Store, error::StoreError};
5use serde::Deserialize;
6use serde_json::{Value, json};
7
8use crate::utils::RpcErr;
9
10#[derive(Clone, Debug)]
11pub enum BlockIdentifier {
12    Number(BlockNumber),
13    Tag(BlockTag),
14}
15
16#[derive(Clone, Debug)]
17pub enum BlockIdentifierOrHash {
18    Hash(BlockHash),
19    Identifier(BlockIdentifier),
20}
21
22#[derive(Deserialize, Default, Clone, Debug, PartialEq)]
23#[serde(rename_all = "camelCase")]
24pub enum BlockTag {
25    Earliest,
26    Finalized,
27    Safe,
28    #[default]
29    Latest,
30    Pending,
31}
32
33impl BlockIdentifier {
34    pub async fn resolve_block_number(
35        &self,
36        storage: &Store,
37    ) -> Result<Option<BlockNumber>, StoreError> {
38        match self {
39            BlockIdentifier::Number(num) => Ok(Some(*num)),
40            BlockIdentifier::Tag(tag) => match tag {
41                BlockTag::Earliest => Ok(Some(storage.get_earliest_block_number().await?)),
42                BlockTag::Finalized => storage.get_finalized_block_number().await,
43                BlockTag::Safe => storage.get_safe_block_number().await,
44                BlockTag::Latest => Ok(Some(storage.get_latest_block_number().await?)),
45                BlockTag::Pending => {
46                    // TODO(#1112): We need to check individual intrincacies of the pending tag for
47                    // each RPC method that uses it.
48                    if let Some(pending_block_number) = storage.get_pending_block_number().await? {
49                        Ok(Some(pending_block_number))
50                    } else {
51                        // If there are no pending blocks, we return the latest block number
52                        Ok(Some(storage.get_latest_block_number().await?))
53                    }
54                }
55            },
56        }
57    }
58
59    pub fn parse(serde_value: Value, arg_index: u64) -> Result<Self, RpcErr> {
60        // Check if it is a BlockTag
61        if let Ok(tag) = serde_json::from_value::<BlockTag>(serde_value.clone()) {
62            return Ok(BlockIdentifier::Tag(tag));
63        };
64        // Parse BlockNumber
65        let hex_str = match serde_json::from_value::<String>(serde_value) {
66            Ok(hex_str) => hex_str,
67            Err(error) => return Err(RpcErr::BadParams(error.to_string())),
68        };
69        // Check that the BlockNumber is 0x prefixed
70        let Some(hex_str) = hex_str.strip_prefix("0x") else {
71            return Err(RpcErr::BadHexFormat(arg_index));
72        };
73
74        // Parse hex string
75        let Ok(block_number) = u64::from_str_radix(hex_str, 16) else {
76            return Err(RpcErr::BadHexFormat(arg_index));
77        };
78        Ok(BlockIdentifier::Number(block_number))
79    }
80
81    pub async fn resolve_block_header(
82        &self,
83        storage: &Store,
84    ) -> Result<Option<BlockHeader>, StoreError> {
85        match self.resolve_block_number(storage).await? {
86            Some(block_number) => storage.get_block_header(block_number),
87            _ => Ok(None),
88        }
89    }
90}
91
92impl BlockIdentifierOrHash {
93    pub async fn resolve_block_header(
94        &self,
95        storage: &Store,
96    ) -> Result<Option<BlockHeader>, StoreError> {
97        match self.resolve_block_number(storage).await? {
98            Some(block_number) => storage.get_block_header(block_number),
99            _ => Ok(None),
100        }
101    }
102
103    pub async fn resolve_block_number(
104        &self,
105        storage: &Store,
106    ) -> Result<Option<BlockNumber>, StoreError> {
107        match self {
108            BlockIdentifierOrHash::Identifier(id) => id.resolve_block_number(storage).await,
109            BlockIdentifierOrHash::Hash(block_hash) => storage.get_block_number(*block_hash).await,
110        }
111    }
112
113    pub fn parse(serde_value: Value, arg_index: u64) -> Result<BlockIdentifierOrHash, RpcErr> {
114        // EIP-1898 object form: {"blockHash": "0x.."} or {"blockNumber": "0x.." | tag}.
115        // `requireCanonical` is accepted but not enforced (matches geth's permissive behavior).
116        if let Value::Object(map) = &serde_value {
117            if map.contains_key("blockHash") && map.contains_key("blockNumber") {
118                return Err(RpcErr::BadParams(
119                    "EIP-1898 block identifier cannot specify both `blockHash` and `blockNumber`"
120                        .to_string(),
121                ));
122            }
123            if let Some(hash_value) = map.get("blockHash") {
124                let hex_str = serde_json::from_value::<String>(hash_value.clone())
125                    .map_err(|e| RpcErr::BadParams(e.to_string()))?;
126                let block_hash =
127                    BlockHash::from_str(&hex_str).map_err(|_| RpcErr::BadHexFormat(arg_index))?;
128                return Ok(BlockIdentifierOrHash::Hash(block_hash));
129            }
130            if let Some(number_value) = map.get("blockNumber") {
131                return BlockIdentifier::parse(number_value.clone(), arg_index)
132                    .map(BlockIdentifierOrHash::Identifier);
133            }
134            return Err(RpcErr::BadParams(
135                "EIP-1898 block identifier requires `blockHash` or `blockNumber`".to_string(),
136            ));
137        }
138
139        // Parse as BlockHash
140        if let Some(block_hash) = serde_json::from_value::<String>(serde_value.clone())
141            .ok()
142            .and_then(|hex_str| BlockHash::from_str(&hex_str).ok())
143        {
144            Ok(BlockIdentifierOrHash::Hash(block_hash))
145        } else {
146            // Parse as BlockIdentifier
147            BlockIdentifier::parse(serde_value, arg_index).map(BlockIdentifierOrHash::Identifier)
148        }
149    }
150
151    #[allow(unused)]
152    pub async fn is_latest(&self, storage: &Store) -> Result<bool, StoreError> {
153        if self == &BlockTag::Latest {
154            return Ok(true);
155        }
156
157        let result = self.resolve_block_number(storage).await?;
158        let latest = storage.get_latest_block_number().await?;
159
160        Ok(result.is_some_and(|res| res == latest))
161    }
162}
163
164impl From<BlockIdentifier> for Value {
165    fn from(value: BlockIdentifier) -> Self {
166        match value {
167            BlockIdentifier::Number(n) => json!(format!("{n:#x}")),
168            BlockIdentifier::Tag(tag) => match tag {
169                BlockTag::Earliest => json!("earliest"),
170                BlockTag::Finalized => json!("finalized"),
171                BlockTag::Safe => json!("safe"),
172                BlockTag::Latest => json!("latest"),
173                BlockTag::Pending => json!("pending"),
174            },
175        }
176    }
177}
178
179impl Display for BlockIdentifier {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        match self {
182            BlockIdentifier::Number(num) => num.fmt(f),
183            BlockIdentifier::Tag(tag) => match tag {
184                BlockTag::Earliest => "earliest".fmt(f),
185                BlockTag::Finalized => "finalized".fmt(f),
186                BlockTag::Safe => "safe".fmt(f),
187                BlockTag::Latest => "latest".fmt(f),
188                BlockTag::Pending => "pending".fmt(f),
189            },
190        }
191    }
192}
193
194impl Default for BlockIdentifier {
195    fn default() -> BlockIdentifier {
196        BlockIdentifier::Tag(BlockTag::default())
197    }
198}
199
200impl Display for BlockIdentifierOrHash {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        match self {
203            BlockIdentifierOrHash::Identifier(id) => id.fmt(f),
204            BlockIdentifierOrHash::Hash(hash) => hash.fmt(f),
205        }
206    }
207}
208
209impl PartialEq<BlockTag> for BlockIdentifierOrHash {
210    fn eq(&self, other: &BlockTag) -> bool {
211        match self {
212            BlockIdentifierOrHash::Identifier(BlockIdentifier::Tag(tag)) => tag == other,
213            _ => false,
214        }
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use serde_json::json;
222
223    #[test]
224    fn parse_eip1898_block_hash_object() {
225        let hash = "0x32a2b8016bfefb8a25030cbc6636a833584bd6ae0db2e2db7176f27a431c5563";
226        let parsed = BlockIdentifierOrHash::parse(json!({"blockHash": hash}), 0).unwrap();
227        match parsed {
228            BlockIdentifierOrHash::Hash(h) => assert_eq!(format!("{h:#x}"), hash),
229            other => panic!("expected Hash, got {other:?}"),
230        }
231    }
232
233    #[test]
234    fn parse_eip1898_block_hash_object_with_require_canonical() {
235        let hash = "0x32a2b8016bfefb8a25030cbc6636a833584bd6ae0db2e2db7176f27a431c5563";
236        let parsed =
237            BlockIdentifierOrHash::parse(json!({"blockHash": hash, "requireCanonical": true}), 0)
238                .unwrap();
239        assert!(matches!(parsed, BlockIdentifierOrHash::Hash(_)));
240    }
241
242    #[test]
243    fn parse_eip1898_block_number_object_hex() {
244        let parsed = BlockIdentifierOrHash::parse(json!({"blockNumber": "0x10"}), 0).unwrap();
245        match parsed {
246            BlockIdentifierOrHash::Identifier(BlockIdentifier::Number(n)) => assert_eq!(n, 16),
247            other => panic!("expected Number(16), got {other:?}"),
248        }
249    }
250
251    #[test]
252    fn parse_eip1898_block_number_object_tag() {
253        let parsed = BlockIdentifierOrHash::parse(json!({"blockNumber": "latest"}), 0).unwrap();
254        assert_eq!(parsed, BlockTag::Latest);
255    }
256
257    #[test]
258    fn parse_eip1898_object_missing_keys_fails() {
259        let err = BlockIdentifierOrHash::parse(json!({}), 0).unwrap_err();
260        assert!(matches!(err, RpcErr::BadParams(_)));
261    }
262
263    #[test]
264    fn parse_eip1898_object_both_keys_fails() {
265        let value = json!({
266            "blockHash": "0x32a2b8016bfefb8a25030cbc6636a833584bd6ae0db2e2db7176f27a431c5563",
267            "blockNumber": "0x10",
268        });
269        let err = BlockIdentifierOrHash::parse(value, 0).unwrap_err();
270        assert!(matches!(err, RpcErr::BadParams(_)));
271    }
272
273    #[test]
274    fn parse_string_forms_still_work() {
275        let hash = "0x32a2b8016bfefb8a25030cbc6636a833584bd6ae0db2e2db7176f27a431c5563";
276        assert!(matches!(
277            BlockIdentifierOrHash::parse(json!(hash), 0).unwrap(),
278            BlockIdentifierOrHash::Hash(_)
279        ));
280        assert!(matches!(
281            BlockIdentifierOrHash::parse(json!("0x10"), 0).unwrap(),
282            BlockIdentifierOrHash::Identifier(BlockIdentifier::Number(16))
283        ));
284        assert_eq!(
285            BlockIdentifierOrHash::parse(json!("latest"), 0).unwrap(),
286            BlockTag::Latest
287        );
288    }
289}