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 if let Some(pending_block_number) = storage.get_pending_block_number().await? {
49 Ok(Some(pending_block_number))
50 } else {
51 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 if let Ok(tag) = serde_json::from_value::<BlockTag>(serde_value.clone()) {
62 return Ok(BlockIdentifier::Tag(tag));
63 };
64 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 let Some(hex_str) = hex_str.strip_prefix("0x") else {
71 return Err(RpcErr::BadHexFormat(arg_index));
72 };
73
74 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 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 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 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}