use eth_valkyoth_codec::{
DecodeError, DecodeLimits, RlpInteger, RlpItem, RlpScalar, decode_rlp_list,
};
use eth_valkyoth_hash::{Keccak256, hash_one};
use eth_valkyoth_primitives::{Address, B256, BlockNumber, Gas, UnixTimestamp, Wei};
mod error;
pub use error::{BlockHeaderDecodeError, BlockHeaderDecodeErrorCategory, BlockHeaderField};
const ADDRESS_BYTES: usize = 20;
const B256_BYTES: usize = 32;
const BLOOM_BYTES: usize = 256;
const NONCE_BYTES: usize = 8;
pub const LEGACY_HEADER_FIELD_COUNT: usize = 15;
pub const LONDON_HEADER_FIELD_COUNT: usize = 16;
pub const SHANGHAI_HEADER_FIELD_COUNT: usize = 17;
pub const CANCUN_HEADER_FIELD_COUNT: usize = 20;
pub const PRAGUE_HEADER_FIELD_COUNT: usize = 21;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HeaderFieldSet {
Legacy,
London,
Shanghai,
Cancun,
Prague,
}
impl HeaderFieldSet {
#[must_use]
pub const fn field_count(self) -> usize {
match self {
Self::Legacy => LEGACY_HEADER_FIELD_COUNT,
Self::London => LONDON_HEADER_FIELD_COUNT,
Self::Shanghai => SHANGHAI_HEADER_FIELD_COUNT,
Self::Cancun => CANCUN_HEADER_FIELD_COUNT,
Self::Prague => PRAGUE_HEADER_FIELD_COUNT,
}
}
const fn has_base_fee(self) -> bool {
!matches!(self, Self::Legacy)
}
const fn has_withdrawals_root(self) -> bool {
matches!(self, Self::Shanghai | Self::Cancun | Self::Prague)
}
const fn has_cancun_fields(self) -> bool {
matches!(self, Self::Cancun | Self::Prague)
}
const fn has_requests_hash(self) -> bool {
matches!(self, Self::Prague)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BlockHash(B256);
impl BlockHash {
#[must_use]
pub const fn from_b256(value: B256) -> Self {
Self(value)
}
#[must_use]
pub const fn to_b256(self) -> B256 {
self.0
}
}
impl From<BlockHash> for B256 {
fn from(value: BlockHash) -> Self {
value.to_b256()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UnvalidatedBlockHeader<'a> {
encoded_rlp: &'a [u8],
pub field_set: HeaderFieldSet,
pub parent_hash: B256,
pub ommers_hash: B256,
pub beneficiary: Address,
pub state_root: B256,
pub transactions_root: B256,
pub receipts_root: B256,
pub logs_bloom: LogsBloom,
pub difficulty: [u8; 32],
pub number: BlockNumber,
pub gas_limit: Gas,
pub gas_used: Gas,
pub timestamp: UnixTimestamp,
pub extra_data: &'a [u8],
pub mix_hash: B256,
pub nonce: [u8; 8],
pub base_fee_per_gas: Option<Wei>,
pub withdrawals_root: Option<B256>,
pub blob_gas_used: Option<Gas>,
pub excess_blob_gas: Option<Gas>,
pub parent_beacon_block_root: Option<B256>,
pub requests_hash: Option<B256>,
}
impl UnvalidatedBlockHeader<'_> {
#[must_use]
pub const fn encoded_rlp(&self) -> &[u8] {
self.encoded_rlp
}
#[must_use]
pub fn hash_with<H>(&self, hasher: H) -> BlockHash
where
H: Keccak256,
{
BlockHash::from_b256(hash_one(hasher, self.encoded_rlp))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LogsBloom([u8; BLOOM_BYTES]);
impl LogsBloom {
#[must_use]
pub const fn from_bytes(bytes: [u8; BLOOM_BYTES]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn to_bytes(self) -> [u8; BLOOM_BYTES] {
self.0
}
}
pub fn decode_block_header<'a>(
input: &'a [u8],
field_set: HeaderFieldSet,
limits: DecodeLimits,
) -> Result<UnvalidatedBlockHeader<'a>, BlockHeaderDecodeError> {
let list = decode_rlp_list(input, limits).map_err(BlockHeaderDecodeError::Decode)?;
let expected = field_set.field_count();
let found = list.item_count();
if found != expected {
return Err(BlockHeaderDecodeError::WrongFieldCount { expected, found });
}
let mut fields = list.items();
let header = UnvalidatedBlockHeader {
encoded_rlp: input,
field_set,
parent_hash: decode_b256(&mut fields, BlockHeaderField::ParentHash)?,
ommers_hash: decode_b256(&mut fields, BlockHeaderField::OmmersHash)?,
beneficiary: decode_address(&mut fields, BlockHeaderField::Beneficiary)?,
state_root: decode_b256(&mut fields, BlockHeaderField::StateRoot)?,
transactions_root: decode_b256(&mut fields, BlockHeaderField::TransactionsRoot)?,
receipts_root: decode_b256(&mut fields, BlockHeaderField::ReceiptsRoot)?,
logs_bloom: decode_logs_bloom(&mut fields)?,
difficulty: decode_u256(&mut fields, BlockHeaderField::Difficulty)?,
number: BlockNumber::new(decode_u64(&mut fields, BlockHeaderField::Number)?),
gas_limit: Gas::new(decode_u64(&mut fields, BlockHeaderField::GasLimit)?),
gas_used: Gas::new(decode_u64(&mut fields, BlockHeaderField::GasUsed)?),
timestamp: UnixTimestamp::new(decode_u64(&mut fields, BlockHeaderField::Timestamp)?),
extra_data: next_scalar(&mut fields, BlockHeaderField::ExtraData)?.payload(),
mix_hash: decode_b256(&mut fields, BlockHeaderField::MixHash)?,
nonce: decode_fixed::<NONCE_BYTES>(&mut fields, BlockHeaderField::Nonce)?,
base_fee_per_gas: decode_optional_wei(
&mut fields,
field_set.has_base_fee(),
BlockHeaderField::BaseFeePerGas,
)?,
withdrawals_root: decode_optional_b256(
&mut fields,
field_set.has_withdrawals_root(),
BlockHeaderField::WithdrawalsRoot,
)?,
blob_gas_used: decode_optional_gas(
&mut fields,
field_set.has_cancun_fields(),
BlockHeaderField::BlobGasUsed,
)?,
excess_blob_gas: decode_optional_gas(
&mut fields,
field_set.has_cancun_fields(),
BlockHeaderField::ExcessBlobGas,
)?,
parent_beacon_block_root: decode_optional_b256(
&mut fields,
field_set.has_cancun_fields(),
BlockHeaderField::ParentBeaconBlockRoot,
)?,
requests_hash: decode_optional_b256(
&mut fields,
field_set.has_requests_hash(),
BlockHeaderField::RequestsHash,
)?,
};
Ok(header)
}
fn next_scalar<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
field: BlockHeaderField,
) -> Result<RlpScalar<'a>, BlockHeaderDecodeError> {
let item = fields
.next()
.ok_or(BlockHeaderDecodeError::FieldDecode {
field,
source: DecodeError::Malformed,
})?
.map_err(|source| BlockHeaderDecodeError::FieldDecode { field, source })?;
match item {
RlpItem::Scalar(scalar) => Ok(scalar),
RlpItem::List(_) => Err(BlockHeaderDecodeError::FieldDecode {
field,
source: DecodeError::UnexpectedList,
}),
}
}
fn decode_u64<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
field: BlockHeaderField,
) -> Result<u64, BlockHeaderDecodeError> {
RlpInteger::try_from_scalar(next_scalar(fields, field)?)
.and_then(RlpInteger::to_u64)
.map_err(|source| BlockHeaderDecodeError::FieldDecode { field, source })
}
fn decode_u256<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
field: BlockHeaderField,
) -> Result<[u8; 32], BlockHeaderDecodeError> {
RlpInteger::try_from_scalar(next_scalar(fields, field)?)
.and_then(RlpInteger::to_be_bytes32)
.map_err(|source| BlockHeaderDecodeError::FieldDecode { field, source })
}
fn decode_wei<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
field: BlockHeaderField,
) -> Result<Wei, BlockHeaderDecodeError> {
decode_u256(fields, field).map(Wei::from_be_bytes)
}
fn decode_fixed<'a, const N: usize>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
field: BlockHeaderField,
) -> Result<[u8; N], BlockHeaderDecodeError> {
let scalar = next_scalar(fields, field)?;
scalar
.payload()
.try_into()
.map_err(|_| BlockHeaderDecodeError::InvalidFieldLength {
field,
expected: N,
found: scalar.payload().len(),
})
}
fn decode_b256<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
field: BlockHeaderField,
) -> Result<B256, BlockHeaderDecodeError> {
decode_fixed::<B256_BYTES>(fields, field).map(B256::from_bytes)
}
fn decode_address<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
field: BlockHeaderField,
) -> Result<Address, BlockHeaderDecodeError> {
decode_fixed::<ADDRESS_BYTES>(fields, field).map(Address::from_bytes)
}
fn decode_logs_bloom<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
) -> Result<LogsBloom, BlockHeaderDecodeError> {
decode_fixed::<BLOOM_BYTES>(fields, BlockHeaderField::LogsBloom).map(LogsBloom::from_bytes)
}
fn decode_optional_b256<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
present: bool,
field: BlockHeaderField,
) -> Result<Option<B256>, BlockHeaderDecodeError> {
if present {
decode_b256(fields, field).map(Some)
} else {
Ok(None)
}
}
fn decode_optional_gas<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
present: bool,
field: BlockHeaderField,
) -> Result<Option<Gas>, BlockHeaderDecodeError> {
if present {
decode_u64(fields, field).map(Gas::new).map(Some)
} else {
Ok(None)
}
}
fn decode_optional_wei<'a>(
fields: &mut impl Iterator<Item = Result<RlpItem<'a>, DecodeError>>,
present: bool,
field: BlockHeaderField,
) -> Result<Option<Wei>, BlockHeaderDecodeError> {
if present {
decode_wei(fields, field).map(Some)
} else {
Ok(None)
}
}
#[cfg(test)]
#[path = "header_tests.rs"]
mod tests;