diff --git a/crates/eips/src/eip1898.rs b/crates/eips/src/eip1898.rs index 0b417658f4b..5dff627bd49 100644 --- a/crates/eips/src/eip1898.rs +++ b/crates/eips/src/eip1898.rs @@ -1,6 +1,7 @@ //! [EIP-1898]: https://eips.ethereum.org/EIPS/eip-1898 -use alloy_primitives::{hex::FromHexError, ruint::ParseError, B256, U64}; +use alloy_primitives::{hex::FromHexError, ruint::ParseError, BlockHash, BlockNumber, B256, U64}; +use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError}; use core::{ fmt::{self, Debug, Display, Formatter}, num::ParseIntError, @@ -556,6 +557,183 @@ impl FromStr for BlockId { } } +/// Block number and hash. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct BlockNumHash { + /// Block number + pub number: BlockNumber, + /// Block hash + pub hash: BlockHash, +} + +/// Block number and hash of the forked block. +pub type ForkBlock = BlockNumHash; + +impl BlockNumHash { + /// Creates a new `BlockNumHash` from a block number and hash. + pub const fn new(number: BlockNumber, hash: BlockHash) -> Self { + Self { number, hash } + } + + /// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`] + pub const fn into_components(self) -> (BlockNumber, BlockHash) { + (self.number, self.hash) + } + + /// Returns whether or not the block matches the given [BlockHashOrNumber]. + pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool { + match block { + BlockHashOrNumber::Hash(hash) => self.hash == *hash, + BlockHashOrNumber::Number(number) => self.number == *number, + } + } +} + +impl From<(BlockNumber, BlockHash)> for BlockNumHash { + fn from(val: (BlockNumber, BlockHash)) -> Self { + BlockNumHash { number: val.0, hash: val.1 } + } +} + +impl From<(BlockHash, BlockNumber)> for BlockNumHash { + fn from(val: (BlockHash, BlockNumber)) -> Self { + BlockNumHash { hash: val.0, number: val.1 } + } +} + +/// Either a block hash _or_ a block number +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + any(test, feature = "arbitrary"), + derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) +)] +pub enum BlockHashOrNumber { + /// A block hash + Hash(B256), + /// A block number + Number(u64), +} + +// === impl BlockHashOrNumber === + +impl BlockHashOrNumber { + /// Returns the block number if it is a [`BlockHashOrNumber::Number`]. + #[inline] + pub const fn as_number(self) -> Option { + match self { + BlockHashOrNumber::Hash(_) => None, + BlockHashOrNumber::Number(num) => Some(num), + } + } +} + +impl From for BlockHashOrNumber { + fn from(value: B256) -> Self { + BlockHashOrNumber::Hash(value) + } +} + +impl From for BlockHashOrNumber { + fn from(value: u64) -> Self { + BlockHashOrNumber::Number(value) + } +} + +impl From for BlockHashOrNumber { + fn from(value: U64) -> Self { + value.to::().into() + } +} + +/// Allows for RLP encoding of either a block hash or block number +impl Encodable for BlockHashOrNumber { + fn encode(&self, out: &mut dyn bytes::BufMut) { + match self { + Self::Hash(block_hash) => block_hash.encode(out), + Self::Number(block_number) => block_number.encode(out), + } + } + fn length(&self) -> usize { + match self { + Self::Hash(block_hash) => block_hash.length(), + Self::Number(block_number) => block_number.length(), + } + } +} + +/// Allows for RLP decoding of a block hash or block number +impl Decodable for BlockHashOrNumber { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?; + // if the byte string is exactly 32 bytes, decode it into a Hash + // 0xa0 = 0x80 (start of string) + 0x20 (32, length of string) + if header == 0xa0 { + // strip the first byte, parsing the rest of the string. + // If the rest of the string fails to decode into 32 bytes, we'll bubble up the + // decoding error. + let hash = B256::decode(buf)?; + Ok(Self::Hash(hash)) + } else { + // a block number when encoded as bytes ranges from 0 to any number of bytes - we're + // going to accept numbers which fit in less than 64 bytes. + // Any data larger than this which is not caught by the Hash decoding should error and + // is considered an invalid block number. + Ok(Self::Number(u64::decode(buf)?)) + } + } +} + +impl fmt::Display for BlockHashOrNumber { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Hash(hash) => write!(f, "{}", hash), + Self::Number(num) => write!(f, "{}", num), + } + } +} + +/// Error thrown when parsing a [BlockHashOrNumber] from a string. +#[derive(Debug)] +pub struct ParseBlockHashOrNumberError { + input: alloc::string::String, + parse_int_error: ParseIntError, + hex_error: alloy_primitives::hex::FromHexError, +} + +impl fmt::Display for ParseBlockHashOrNumberError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "failed to parse {:?} as a number: {} or hash: {}", + self.input, self.parse_int_error, self.hex_error + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseBlockHashOrNumberError {} + +impl FromStr for BlockHashOrNumber { + type Err = ParseBlockHashOrNumberError; + + fn from_str(s: &str) -> Result { + #[allow(unused_imports)] + use alloc::string::ToString; + match u64::from_str(s) { + Ok(val) => Ok(val.into()), + Err(parse_int_error) => match B256::from_str(s) { + Ok(val) => Ok(val.into()), + Err(hex_error) => Err(ParseBlockHashOrNumberError { + input: s.to_string(), + parse_int_error, + hex_error, + }), + }, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/eips/src/lib.rs b/crates/eips/src/lib.rs index c5ed82c66b6..8a519b02116 100644 --- a/crates/eips/src/lib.rs +++ b/crates/eips/src/lib.rs @@ -23,7 +23,9 @@ pub mod eip1559; pub use eip1559::calc_next_block_base_fee; pub mod eip1898; -pub use eip1898::{BlockId, BlockNumberOrTag, RpcBlockHash}; +pub use eip1898::{ + BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash, +}; pub mod eip2718; diff --git a/crates/rpc-types/src/eth/block.rs b/crates/rpc-types/src/eth/block.rs index 61f253e8bc2..d9c8360119f 100644 --- a/crates/rpc-types/src/eth/block.rs +++ b/crates/rpc-types/src/eth/block.rs @@ -4,12 +4,12 @@ use crate::{other::OtherFields, Transaction, Withdrawal}; pub use alloy_eips::{ - calc_blob_gasprice, calc_excess_blob_gas, BlockId, BlockNumberOrTag, RpcBlockHash, + calc_blob_gasprice, calc_excess_blob_gas, BlockHashOrNumber, BlockId, BlockNumHash, + BlockNumberOrTag, ForkBlock, RpcBlockHash, }; -use alloy_primitives::{Address, BlockHash, BlockNumber, Bloom, Bytes, B256, B64, U256, U64}; -use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError}; +use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256, U64}; use serde::{ser::Error, Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, fmt, num::ParseIntError, ops::Deref, str::FromStr}; +use std::{collections::BTreeMap, ops::Deref}; /// Block representation #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -424,174 +424,6 @@ pub enum BlockError { RlpDecodeRawBlock(alloy_rlp::Error), } -/// Block number and hash. -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)] -pub struct BlockNumHash { - /// Block number - pub number: BlockNumber, - /// Block hash - pub hash: BlockHash, -} - -/// Block number and hash of the forked block. -pub type ForkBlock = BlockNumHash; - -impl fmt::Debug for BlockNumHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("").field(&self.number).field(&self.hash).finish() - } -} - -impl BlockNumHash { - /// Creates a new `BlockNumHash` from a block number and hash. - pub const fn new(number: BlockNumber, hash: BlockHash) -> Self { - Self { number, hash } - } - - /// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`] - pub const fn into_components(self) -> (BlockNumber, BlockHash) { - (self.number, self.hash) - } - - /// Returns whether or not the block matches the given [BlockHashOrNumber]. - pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool { - match block { - BlockHashOrNumber::Hash(hash) => self.hash == *hash, - BlockHashOrNumber::Number(number) => self.number == *number, - } - } -} - -impl From<(BlockNumber, BlockHash)> for BlockNumHash { - fn from(val: (BlockNumber, BlockHash)) -> Self { - BlockNumHash { number: val.0, hash: val.1 } - } -} - -impl From<(BlockHash, BlockNumber)> for BlockNumHash { - fn from(val: (BlockHash, BlockNumber)) -> Self { - BlockNumHash { hash: val.0, number: val.1 } - } -} - -/// Either a block hash _or_ a block number -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[cfg_attr( - any(test, feature = "arbitrary"), - derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) -)] -pub enum BlockHashOrNumber { - /// A block hash - Hash(B256), - /// A block number - Number(u64), -} - -// === impl BlockHashOrNumber === - -impl BlockHashOrNumber { - /// Returns the block number if it is a [`BlockHashOrNumber::Number`]. - #[inline] - pub const fn as_number(self) -> Option { - match self { - BlockHashOrNumber::Hash(_) => None, - BlockHashOrNumber::Number(num) => Some(num), - } - } -} - -impl From for BlockHashOrNumber { - fn from(value: B256) -> Self { - BlockHashOrNumber::Hash(value) - } -} - -impl From for BlockHashOrNumber { - fn from(value: u64) -> Self { - BlockHashOrNumber::Number(value) - } -} - -impl From for BlockHashOrNumber { - fn from(value: U64) -> Self { - value.to::().into() - } -} - -/// Allows for RLP encoding of either a block hash or block number -impl Encodable for BlockHashOrNumber { - fn encode(&self, out: &mut dyn bytes::BufMut) { - match self { - Self::Hash(block_hash) => block_hash.encode(out), - Self::Number(block_number) => block_number.encode(out), - } - } - fn length(&self) -> usize { - match self { - Self::Hash(block_hash) => block_hash.length(), - Self::Number(block_number) => block_number.length(), - } - } -} - -/// Allows for RLP decoding of a block hash or block number -impl Decodable for BlockHashOrNumber { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?; - // if the byte string is exactly 32 bytes, decode it into a Hash - // 0xa0 = 0x80 (start of string) + 0x20 (32, length of string) - if header == 0xa0 { - // strip the first byte, parsing the rest of the string. - // If the rest of the string fails to decode into 32 bytes, we'll bubble up the - // decoding error. - let hash = B256::decode(buf)?; - Ok(Self::Hash(hash)) - } else { - // a block number when encoded as bytes ranges from 0 to any number of bytes - we're - // going to accept numbers which fit in less than 64 bytes. - // Any data larger than this which is not caught by the Hash decoding should error and - // is considered an invalid block number. - Ok(Self::Number(u64::decode(buf)?)) - } - } -} - -impl fmt::Display for BlockHashOrNumber { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Hash(hash) => write!(f, "{}", hash), - Self::Number(num) => write!(f, "{}", num), - } - } -} - -/// Error thrown when parsing a [BlockHashOrNumber] from a string. -#[derive(Debug, thiserror::Error)] -#[error("failed to parse {input:?} as a number: {parse_int_error} or hash: {hex_error}")] -pub struct ParseBlockHashOrNumberError { - input: String, - parse_int_error: ParseIntError, - hex_error: alloy_primitives::hex::FromHexError, -} - -impl FromStr for BlockHashOrNumber { - type Err = ParseBlockHashOrNumberError; - - fn from_str(s: &str) -> Result { - match u64::from_str(s) { - Ok(val) => Ok(val.into()), - Err(pares_int_error) => match B256::from_str(s) { - Ok(val) => Ok(val.into()), - Err(hex_error) => Err(ParseBlockHashOrNumberError { - input: s.to_string(), - parse_int_error: pares_int_error, - hex_error, - }), - }, - } - } -} - /// A Block representation that allows to include additional fields pub type RichBlock = Rich;