use std::{fmt::Display, str::FromStr};
use ethrex_common::types::{BlockHash, BlockHeader, BlockNumber};
use ethrex_storage::{Store, error::StoreError};
use serde::Deserialize;
use serde_json::{Value, json};
use crate::utils::RpcErr;
#[derive(Clone, Debug)]
pub enum BlockIdentifier {
Number(BlockNumber),
Tag(BlockTag),
}
#[derive(Clone, Debug)]
pub enum BlockIdentifierOrHash {
Hash(BlockHash),
Identifier(BlockIdentifier),
}
#[derive(Deserialize, Default, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum BlockTag {
Earliest,
Finalized,
Safe,
#[default]
Latest,
Pending,
}
impl BlockIdentifier {
pub async fn resolve_block_number(
&self,
storage: &Store,
) -> Result<Option<BlockNumber>, StoreError> {
match self {
BlockIdentifier::Number(num) => Ok(Some(*num)),
BlockIdentifier::Tag(tag) => match tag {
BlockTag::Earliest => Ok(Some(storage.get_earliest_block_number().await?)),
BlockTag::Finalized => storage.get_finalized_block_number().await,
BlockTag::Safe => storage.get_safe_block_number().await,
BlockTag::Latest => Ok(Some(storage.get_latest_block_number().await?)),
BlockTag::Pending => {
if let Some(pending_block_number) = storage.get_pending_block_number().await? {
Ok(Some(pending_block_number))
} else {
Ok(Some(storage.get_latest_block_number().await?))
}
}
},
}
}
pub fn parse(serde_value: Value, arg_index: u64) -> Result<Self, RpcErr> {
if let Ok(tag) = serde_json::from_value::<BlockTag>(serde_value.clone()) {
return Ok(BlockIdentifier::Tag(tag));
};
let hex_str = match serde_json::from_value::<String>(serde_value) {
Ok(hex_str) => hex_str,
Err(error) => return Err(RpcErr::BadParams(error.to_string())),
};
let Some(hex_str) = hex_str.strip_prefix("0x") else {
return Err(RpcErr::BadHexFormat(arg_index));
};
let Ok(block_number) = u64::from_str_radix(hex_str, 16) else {
return Err(RpcErr::BadHexFormat(arg_index));
};
Ok(BlockIdentifier::Number(block_number))
}
pub async fn resolve_block_header(
&self,
storage: &Store,
) -> Result<Option<BlockHeader>, StoreError> {
match self.resolve_block_number(storage).await? {
Some(block_number) => storage.get_block_header(block_number),
_ => Ok(None),
}
}
}
impl BlockIdentifierOrHash {
pub async fn resolve_block_header(
&self,
storage: &Store,
) -> Result<Option<BlockHeader>, StoreError> {
match self.resolve_block_number(storage).await? {
Some(block_number) => storage.get_block_header(block_number),
_ => Ok(None),
}
}
pub async fn resolve_block_number(
&self,
storage: &Store,
) -> Result<Option<BlockNumber>, StoreError> {
match self {
BlockIdentifierOrHash::Identifier(id) => id.resolve_block_number(storage).await,
BlockIdentifierOrHash::Hash(block_hash) => storage.get_block_number(*block_hash).await,
}
}
pub fn parse(serde_value: Value, arg_index: u64) -> Result<BlockIdentifierOrHash, RpcErr> {
if let Value::Object(map) = &serde_value {
if map.contains_key("blockHash") && map.contains_key("blockNumber") {
return Err(RpcErr::BadParams(
"EIP-1898 block identifier cannot specify both `blockHash` and `blockNumber`"
.to_string(),
));
}
if let Some(hash_value) = map.get("blockHash") {
let hex_str = serde_json::from_value::<String>(hash_value.clone())
.map_err(|e| RpcErr::BadParams(e.to_string()))?;
let block_hash =
BlockHash::from_str(&hex_str).map_err(|_| RpcErr::BadHexFormat(arg_index))?;
return Ok(BlockIdentifierOrHash::Hash(block_hash));
}
if let Some(number_value) = map.get("blockNumber") {
return BlockIdentifier::parse(number_value.clone(), arg_index)
.map(BlockIdentifierOrHash::Identifier);
}
return Err(RpcErr::BadParams(
"EIP-1898 block identifier requires `blockHash` or `blockNumber`".to_string(),
));
}
if let Some(block_hash) = serde_json::from_value::<String>(serde_value.clone())
.ok()
.and_then(|hex_str| BlockHash::from_str(&hex_str).ok())
{
Ok(BlockIdentifierOrHash::Hash(block_hash))
} else {
BlockIdentifier::parse(serde_value, arg_index).map(BlockIdentifierOrHash::Identifier)
}
}
#[allow(unused)]
pub async fn is_latest(&self, storage: &Store) -> Result<bool, StoreError> {
if self == &BlockTag::Latest {
return Ok(true);
}
let result = self.resolve_block_number(storage).await?;
let latest = storage.get_latest_block_number().await?;
Ok(result.is_some_and(|res| res == latest))
}
}
impl From<BlockIdentifier> for Value {
fn from(value: BlockIdentifier) -> Self {
match value {
BlockIdentifier::Number(n) => json!(format!("{n:#x}")),
BlockIdentifier::Tag(tag) => match tag {
BlockTag::Earliest => json!("earliest"),
BlockTag::Finalized => json!("finalized"),
BlockTag::Safe => json!("safe"),
BlockTag::Latest => json!("latest"),
BlockTag::Pending => json!("pending"),
},
}
}
}
impl Display for BlockIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BlockIdentifier::Number(num) => num.fmt(f),
BlockIdentifier::Tag(tag) => match tag {
BlockTag::Earliest => "earliest".fmt(f),
BlockTag::Finalized => "finalized".fmt(f),
BlockTag::Safe => "safe".fmt(f),
BlockTag::Latest => "latest".fmt(f),
BlockTag::Pending => "pending".fmt(f),
},
}
}
}
impl Default for BlockIdentifier {
fn default() -> BlockIdentifier {
BlockIdentifier::Tag(BlockTag::default())
}
}
impl Display for BlockIdentifierOrHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BlockIdentifierOrHash::Identifier(id) => id.fmt(f),
BlockIdentifierOrHash::Hash(hash) => hash.fmt(f),
}
}
}
impl PartialEq<BlockTag> for BlockIdentifierOrHash {
fn eq(&self, other: &BlockTag) -> bool {
match self {
BlockIdentifierOrHash::Identifier(BlockIdentifier::Tag(tag)) => tag == other,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn parse_eip1898_block_hash_object() {
let hash = "0x32a2b8016bfefb8a25030cbc6636a833584bd6ae0db2e2db7176f27a431c5563";
let parsed = BlockIdentifierOrHash::parse(json!({"blockHash": hash}), 0).unwrap();
match parsed {
BlockIdentifierOrHash::Hash(h) => assert_eq!(format!("{h:#x}"), hash),
other => panic!("expected Hash, got {other:?}"),
}
}
#[test]
fn parse_eip1898_block_hash_object_with_require_canonical() {
let hash = "0x32a2b8016bfefb8a25030cbc6636a833584bd6ae0db2e2db7176f27a431c5563";
let parsed =
BlockIdentifierOrHash::parse(json!({"blockHash": hash, "requireCanonical": true}), 0)
.unwrap();
assert!(matches!(parsed, BlockIdentifierOrHash::Hash(_)));
}
#[test]
fn parse_eip1898_block_number_object_hex() {
let parsed = BlockIdentifierOrHash::parse(json!({"blockNumber": "0x10"}), 0).unwrap();
match parsed {
BlockIdentifierOrHash::Identifier(BlockIdentifier::Number(n)) => assert_eq!(n, 16),
other => panic!("expected Number(16), got {other:?}"),
}
}
#[test]
fn parse_eip1898_block_number_object_tag() {
let parsed = BlockIdentifierOrHash::parse(json!({"blockNumber": "latest"}), 0).unwrap();
assert_eq!(parsed, BlockTag::Latest);
}
#[test]
fn parse_eip1898_object_missing_keys_fails() {
let err = BlockIdentifierOrHash::parse(json!({}), 0).unwrap_err();
assert!(matches!(err, RpcErr::BadParams(_)));
}
#[test]
fn parse_eip1898_object_both_keys_fails() {
let value = json!({
"blockHash": "0x32a2b8016bfefb8a25030cbc6636a833584bd6ae0db2e2db7176f27a431c5563",
"blockNumber": "0x10",
});
let err = BlockIdentifierOrHash::parse(value, 0).unwrap_err();
assert!(matches!(err, RpcErr::BadParams(_)));
}
#[test]
fn parse_string_forms_still_work() {
let hash = "0x32a2b8016bfefb8a25030cbc6636a833584bd6ae0db2e2db7176f27a431c5563";
assert!(matches!(
BlockIdentifierOrHash::parse(json!(hash), 0).unwrap(),
BlockIdentifierOrHash::Hash(_)
));
assert!(matches!(
BlockIdentifierOrHash::parse(json!("0x10"), 0).unwrap(),
BlockIdentifierOrHash::Identifier(BlockIdentifier::Number(16))
));
assert_eq!(
BlockIdentifierOrHash::parse(json!("latest"), 0).unwrap(),
BlockTag::Latest
);
}
}