chore: move Header and SealedHeader to reth-primitives-traits (#8831)

This commit is contained in:
joshieDo
2024-06-14 16:43:57 +02:00
committed by GitHub
parent ca574edbc8
commit 217ff958cc
14 changed files with 841 additions and 789 deletions

6
Cargo.lock generated
View File

@ -7723,14 +7723,20 @@ name = "reth-primitives-traits"
version = "1.0.0-rc.1" version = "1.0.0-rc.1"
dependencies = [ dependencies = [
"alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)",
"alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)",
"alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)", "alloy-genesis 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)",
"alloy-primitives", "alloy-primitives",
"alloy-rlp",
"alloy-rpc-types-eth 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=00d81d7)",
"arbitrary", "arbitrary",
"bytes", "bytes",
"derive_more",
"modular-bitfield", "modular-bitfield",
"proptest", "proptest",
"proptest-derive", "proptest-derive",
"rand 0.8.5",
"reth-codecs", "reth-codecs",
"revm-primitives",
"serde", "serde",
"test-fuzz", "test-fuzz",
] ]

View File

@ -357,9 +357,10 @@ alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7",
"eth", "eth",
] } ] }
alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false } alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false }
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false, features = [ alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "00d81d7", default-features = false, features = [

View File

@ -14,9 +14,15 @@ workspace = true
[dependencies] [dependencies]
reth-codecs.workspace = true reth-codecs.workspace = true
alloy-consensus.workspace = true
alloy-eips.workspace = true
alloy-genesis.workspace = true alloy-genesis.workspace = true
alloy-primitives.workspace = true alloy-primitives.workspace = true
alloy-consensus.workspace = true alloy-rlp.workspace = true
alloy-rpc-types-eth = { workspace = true, optional = true }
derive_more.workspace = true
revm-primitives.workspace = true
# required by reth-codecs # required by reth-codecs
modular-bitfield.workspace = true modular-bitfield.workspace = true
@ -33,11 +39,13 @@ arbitrary = { workspace = true, features = ["derive"] }
proptest.workspace = true proptest.workspace = true
proptest-derive.workspace = true proptest-derive.workspace = true
test-fuzz.workspace = true test-fuzz.workspace = true
rand.workspace = true
[features] [features]
test-utils = ["arbitrary"]
arbitrary = [ arbitrary = [
"dep:arbitrary", "dep:arbitrary",
"dep:proptest", "dep:proptest",
"dep:proptest-derive" "dep:proptest-derive"
] ]
alloy-compat = ["alloy-rpc-types-eth"]

View File

@ -0,0 +1,48 @@
use super::Header;
use alloy_rpc_types_eth::{ConversionError, Header as RpcHeader};
impl TryFrom<RpcHeader> for Header {
type Error = ConversionError;
fn try_from(header: RpcHeader) -> Result<Self, Self::Error> {
Ok(Self {
base_fee_per_gas: header
.base_fee_per_gas
.map(|base_fee_per_gas| {
base_fee_per_gas.try_into().map_err(ConversionError::BaseFeePerGasConversion)
})
.transpose()?,
beneficiary: header.miner,
blob_gas_used: header
.blob_gas_used
.map(|blob_gas_used| {
blob_gas_used.try_into().map_err(ConversionError::BlobGasUsedConversion)
})
.transpose()?,
difficulty: header.difficulty,
excess_blob_gas: header
.excess_blob_gas
.map(|excess_blob_gas| {
excess_blob_gas.try_into().map_err(ConversionError::ExcessBlobGasConversion)
})
.transpose()?,
extra_data: header.extra_data,
gas_limit: header.gas_limit.try_into().map_err(ConversionError::GasLimitConversion)?,
gas_used: header.gas_used.try_into().map_err(ConversionError::GasUsedConversion)?,
logs_bloom: header.logs_bloom,
mix_hash: header.mix_hash.unwrap_or_default(),
nonce: u64::from_be_bytes(header.nonce.unwrap_or_default().0),
number: header.number.ok_or(ConversionError::MissingBlockNumber)?,
ommers_hash: header.uncles_hash,
parent_beacon_block_root: header.parent_beacon_block_root,
parent_hash: header.parent_hash,
receipts_root: header.receipts_root,
state_root: header.state_root,
timestamp: header.timestamp,
transactions_root: header.transactions_root,
withdrawals_root: header.withdrawals_root,
// TODO: requests_root: header.requests_root,
requests_root: None,
})
}
}

View File

@ -0,0 +1,8 @@
/// Errors that can occur during header sanity checks.
#[derive(Debug, PartialEq, Eq)]
pub enum HeaderError {
/// Represents an error when the block difficulty is too large.
LargeDifficulty,
/// Represents an error when the block extradata is too large.
LargeExtraData,
}

View File

@ -0,0 +1,509 @@
mod sealed;
pub use sealed::SealedHeader;
mod error;
pub use error::HeaderError;
#[cfg(any(test, feature = "test-utils", feature = "arbitrary"))]
pub mod test_utils;
use alloy_consensus::constants::{EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH};
use alloy_eips::{
calc_next_block_base_fee, eip1559::BaseFeeParams, merge::ALLOWED_FUTURE_BLOCK_TIME_SECONDS,
BlockNumHash,
};
use alloy_primitives::{keccak256, Address, BlockNumber, Bloom, Bytes, B256, B64, U256};
use alloy_rlp::{length_of_length, Decodable, Encodable};
use bytes::BufMut;
use reth_codecs::{main_codec, Compact};
use revm_primitives::{calc_blob_gasprice, calc_excess_blob_gas};
use std::mem;
/// Block header
#[main_codec]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Header {
/// The Keccak 256-bit hash of the parent
/// blocks header, in its entirety; formally Hp.
pub parent_hash: B256,
/// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho.
pub ommers_hash: B256,
/// The 160-bit address to which all fees collected from the successful mining of this block
/// be transferred; formally Hc.
pub beneficiary: Address,
/// The Keccak 256-bit hash of the root node of the state trie, after all transactions are
/// executed and finalisations applied; formally Hr.
pub state_root: B256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with each
/// transaction in the transactions list portion of the block; formally Ht.
pub transactions_root: B256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts
/// of each transaction in the transactions list portion of the block; formally He.
pub receipts_root: B256,
/// The Keccak 256-bit hash of the withdrawals list portion of this block.
///
/// See [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895).
pub withdrawals_root: Option<B256>,
/// The Bloom filter composed from indexable information (logger address and log topics)
/// contained in each log entry from the receipt of each transaction in the transactions list;
/// formally Hb.
pub logs_bloom: Bloom,
/// A scalar value corresponding to the difficulty level of this block. This can be calculated
/// from the previous blocks difficulty level and the timestamp; formally Hd.
pub difficulty: U256,
/// A scalar value equal to the number of ancestor blocks. The genesis block has a number of
/// zero; formally Hi.
pub number: BlockNumber,
/// A scalar value equal to the current limit of gas expenditure per block; formally Hl.
pub gas_limit: u64,
/// A scalar value equal to the total gas used in transactions in this block; formally Hg.
pub gas_used: u64,
/// A scalar value equal to the reasonable output of Unixs time() at this blocks inception;
/// formally Hs.
pub timestamp: u64,
/// A 256-bit hash which, combined with the
/// nonce, proves that a sufficient amount of computation has been carried out on this block;
/// formally Hm.
pub mix_hash: B256,
/// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of
/// computation has been carried out on this block; formally Hn.
pub nonce: u64,
/// A scalar representing EIP1559 base fee which can move up or down each block according
/// to a formula which is a function of gas used in parent block and gas target
/// (block gas limit divided by elasticity multiplier) of parent block.
/// The algorithm results in the base fee per gas increasing when blocks are
/// above the gas target, and decreasing when blocks are below the gas target. The base fee per
/// gas is burned.
pub base_fee_per_gas: Option<u64>,
/// The total amount of blob gas consumed by the transactions within the block, added in
/// EIP-4844.
pub blob_gas_used: Option<u64>,
/// A running total of blob gas consumed in excess of the target, prior to the block. Blocks
/// with above-target blob gas consumption increase this value, blocks with below-target blob
/// gas consumption decrease it (bounded at 0). This was added in EIP-4844.
pub excess_blob_gas: Option<u64>,
/// The hash of the parent beacon block's root is included in execution blocks, as proposed by
/// EIP-4788.
///
/// This enables trust-minimized access to consensus state, supporting staking pools, bridges,
/// and more.
///
/// The beacon roots contract handles root storage, enhancing Ethereum's functionalities.
pub parent_beacon_block_root: Option<B256>,
/// The Keccak 256-bit hash of the root node of the trie structure populated with each
/// [EIP-7685] request in the block body.
///
/// [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685
pub requests_root: Option<B256>,
/// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or
/// fewer; formally Hx.
pub extra_data: Bytes,
}
impl AsRef<Self> for Header {
fn as_ref(&self) -> &Self {
self
}
}
impl Default for Header {
fn default() -> Self {
Self {
parent_hash: Default::default(),
ommers_hash: EMPTY_OMMER_ROOT_HASH,
beneficiary: Default::default(),
state_root: EMPTY_ROOT_HASH,
transactions_root: EMPTY_ROOT_HASH,
receipts_root: EMPTY_ROOT_HASH,
logs_bloom: Default::default(),
difficulty: Default::default(),
number: 0,
gas_limit: 0,
gas_used: 0,
timestamp: 0,
extra_data: Default::default(),
mix_hash: Default::default(),
nonce: 0,
base_fee_per_gas: None,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_root: None,
}
}
}
impl Header {
/// Checks if the block's difficulty is set to zero, indicating a Proof-of-Stake header.
///
/// This function is linked to EIP-3675, proposing the consensus upgrade to Proof-of-Stake:
/// [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#replacing-difficulty-with-0)
///
/// Verifies whether, as per the EIP, the block's difficulty is updated to zero,
/// signifying the transition to a Proof-of-Stake mechanism.
///
/// Returns `true` if the block's difficulty matches the constant zero set by the EIP.
pub fn is_zero_difficulty(&self) -> bool {
self.difficulty.is_zero()
}
/// Performs a sanity check on the extradata field of the header.
///
/// # Errors
///
/// Returns an error if the extradata size is larger than 100 KB.
pub fn ensure_extradata_valid(&self) -> Result<(), HeaderError> {
if self.extra_data.len() > 100 * 1024 {
return Err(HeaderError::LargeExtraData)
}
Ok(())
}
/// Performs a sanity check on the block difficulty field of the header.
///
/// # Errors
///
/// Returns an error if the block difficulty exceeds 80 bits.
pub fn ensure_difficulty_valid(&self) -> Result<(), HeaderError> {
if self.difficulty.bit_len() > 80 {
return Err(HeaderError::LargeDifficulty)
}
Ok(())
}
/// Performs combined sanity checks on multiple header fields.
///
/// This method combines checks for block difficulty and extradata sizes.
///
/// # Errors
///
/// Returns an error if either the block difficulty exceeds 80 bits
/// or if the extradata size is larger than 100 KB.
pub fn ensure_well_formed(&self) -> Result<(), HeaderError> {
self.ensure_difficulty_valid()?;
self.ensure_extradata_valid()?;
Ok(())
}
/// Checks if the block's timestamp is in the past compared to the parent block's timestamp.
///
/// Note: This check is relevant only pre-merge.
pub const fn is_timestamp_in_past(&self, parent_timestamp: u64) -> bool {
self.timestamp <= parent_timestamp
}
/// Checks if the block's timestamp is in the future based on the present timestamp.
///
/// Clock can drift but this can be consensus issue.
///
/// Note: This check is relevant only pre-merge.
pub const fn exceeds_allowed_future_timestamp(&self, present_timestamp: u64) -> bool {
self.timestamp > present_timestamp + ALLOWED_FUTURE_BLOCK_TIME_SECONDS
}
/// Returns the parent block's number and hash
pub const fn parent_num_hash(&self) -> BlockNumHash {
BlockNumHash { number: self.number.saturating_sub(1), hash: self.parent_hash }
}
/// Heavy function that will calculate hash of data and will *not* save the change to metadata.
/// Use [`Header::seal`], [`SealedHeader`] and unlock if you need hash to be persistent.
pub fn hash_slow(&self) -> B256 {
keccak256(alloy_rlp::encode(self))
}
/// Checks if the header is empty - has no transactions and no ommers
pub fn is_empty(&self) -> bool {
self.transaction_root_is_empty() &&
self.ommers_hash_is_empty() &&
self.withdrawals_root.map_or(true, |root| root == EMPTY_ROOT_HASH)
}
/// Check if the ommers hash equals to empty hash list.
pub fn ommers_hash_is_empty(&self) -> bool {
self.ommers_hash == EMPTY_OMMER_ROOT_HASH
}
/// Check if the transaction root equals to empty root.
pub fn transaction_root_is_empty(&self) -> bool {
self.transactions_root == EMPTY_ROOT_HASH
}
/// Returns the blob fee for _this_ block according to the EIP-4844 spec.
///
/// Returns `None` if `excess_blob_gas` is None
pub fn blob_fee(&self) -> Option<u128> {
self.excess_blob_gas.map(calc_blob_gasprice)
}
/// Returns the blob fee for the next block according to the EIP-4844 spec.
///
/// Returns `None` if `excess_blob_gas` is None.
///
/// See also [`Self::next_block_excess_blob_gas`]
pub fn next_block_blob_fee(&self) -> Option<u128> {
self.next_block_excess_blob_gas().map(calc_blob_gasprice)
}
/// Calculate base fee for next block according to the EIP-1559 spec.
///
/// Returns a `None` if no base fee is set, no EIP-1559 support
pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
Some(calc_next_block_base_fee(
self.gas_used as u128,
self.gas_limit as u128,
self.base_fee_per_gas? as u128,
base_fee_params,
) as u64)
}
/// Calculate excess blob gas for the next block according to the EIP-4844 spec.
///
/// Returns a `None` if no excess blob gas is set, no EIP-4844 support
pub fn next_block_excess_blob_gas(&self) -> Option<u64> {
Some(calc_excess_blob_gas(self.excess_blob_gas?, self.blob_gas_used?))
}
/// Seal the header with a known hash.
///
/// WARNING: This method does not perform validation whether the hash is correct.
#[inline]
pub const fn seal(self, hash: B256) -> SealedHeader {
SealedHeader::new(self, hash)
}
/// Calculate hash and seal the Header so that it can't be changed.
#[inline]
pub fn seal_slow(self) -> SealedHeader {
let hash = self.hash_slow();
self.seal(hash)
}
/// Calculate a heuristic for the in-memory size of the [Header].
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<B256>() + // parent hash
mem::size_of::<B256>() + // ommers hash
mem::size_of::<Address>() + // beneficiary
mem::size_of::<B256>() + // state root
mem::size_of::<B256>() + // transactions root
mem::size_of::<B256>() + // receipts root
mem::size_of::<Option<B256>>() + // withdrawals root
mem::size_of::<Bloom>() + // logs bloom
mem::size_of::<U256>() + // difficulty
mem::size_of::<BlockNumber>() + // number
mem::size_of::<u64>() + // gas limit
mem::size_of::<u64>() + // gas used
mem::size_of::<u64>() + // timestamp
mem::size_of::<B256>() + // mix hash
mem::size_of::<u64>() + // nonce
mem::size_of::<Option<u64>>() + // base fee per gas
mem::size_of::<Option<u64>>() + // blob gas used
mem::size_of::<Option<u64>>() + // excess blob gas
mem::size_of::<Option<B256>>() + // parent beacon block root
self.extra_data.len() // extra data
}
fn header_payload_length(&self) -> usize {
let mut length = 0;
length += self.parent_hash.length(); // Hash of the previous block.
length += self.ommers_hash.length(); // Hash of uncle blocks.
length += self.beneficiary.length(); // Address that receives rewards.
length += self.state_root.length(); // Root hash of the state object.
length += self.transactions_root.length(); // Root hash of transactions in the block.
length += self.receipts_root.length(); // Hash of transaction receipts.
length += self.logs_bloom.length(); // Data structure containing event logs.
length += self.difficulty.length(); // Difficulty value of the block.
length += U256::from(self.number).length(); // Block number.
length += U256::from(self.gas_limit).length(); // Maximum gas allowed.
length += U256::from(self.gas_used).length(); // Actual gas used.
length += self.timestamp.length(); // Block timestamp.
length += self.extra_data.length(); // Additional arbitrary data.
length += self.mix_hash.length(); // Hash used for mining.
length += B64::new(self.nonce.to_be_bytes()).length(); // Nonce for mining.
if let Some(base_fee) = self.base_fee_per_gas {
// Adding base fee length if it exists.
length += U256::from(base_fee).length();
}
if let Some(root) = self.withdrawals_root {
// Adding withdrawals_root length if it exists.
length += root.length();
}
if let Some(blob_gas_used) = self.blob_gas_used {
// Adding blob_gas_used length if it exists.
length += U256::from(blob_gas_used).length();
}
if let Some(excess_blob_gas) = self.excess_blob_gas {
// Adding excess_blob_gas length if it exists.
length += U256::from(excess_blob_gas).length();
}
if let Some(parent_beacon_block_root) = self.parent_beacon_block_root {
length += parent_beacon_block_root.length();
}
if let Some(requests_root) = self.requests_root {
length += requests_root.length();
}
length
}
}
impl Encodable for Header {
fn encode(&self, out: &mut dyn BufMut) {
// Create a header indicating the encoded content is a list with the payload length computed
// from the header's payload calculation function.
let list_header =
alloy_rlp::Header { list: true, payload_length: self.header_payload_length() };
list_header.encode(out);
// Encode each header field sequentially
self.parent_hash.encode(out); // Encode parent hash.
self.ommers_hash.encode(out); // Encode ommer's hash.
self.beneficiary.encode(out); // Encode beneficiary.
self.state_root.encode(out); // Encode state root.
self.transactions_root.encode(out); // Encode transactions root.
self.receipts_root.encode(out); // Encode receipts root.
self.logs_bloom.encode(out); // Encode logs bloom.
self.difficulty.encode(out); // Encode difficulty.
U256::from(self.number).encode(out); // Encode block number.
U256::from(self.gas_limit).encode(out); // Encode gas limit.
U256::from(self.gas_used).encode(out); // Encode gas used.
self.timestamp.encode(out); // Encode timestamp.
self.extra_data.encode(out); // Encode extra data.
self.mix_hash.encode(out); // Encode mix hash.
B64::new(self.nonce.to_be_bytes()).encode(out); // Encode nonce.
// Encode base fee. Put empty list if base fee is missing,
// but withdrawals root is present.
if let Some(ref base_fee) = self.base_fee_per_gas {
U256::from(*base_fee).encode(out);
}
// Encode withdrawals root. Put empty string if withdrawals root is missing,
// but blob gas used is present.
if let Some(ref root) = self.withdrawals_root {
root.encode(out);
}
// Encode blob gas used. Put empty list if blob gas used is missing,
// but excess blob gas is present.
if let Some(ref blob_gas_used) = self.blob_gas_used {
U256::from(*blob_gas_used).encode(out);
}
// Encode excess blob gas. Put empty list if excess blob gas is missing,
// but parent beacon block root is present.
if let Some(ref excess_blob_gas) = self.excess_blob_gas {
U256::from(*excess_blob_gas).encode(out);
}
// Encode parent beacon block root.
if let Some(ref parent_beacon_block_root) = self.parent_beacon_block_root {
parent_beacon_block_root.encode(out);
}
// Encode EIP-7685 requests root
//
// If new fields are added, the above pattern will need to
// be repeated and placeholders added. Otherwise, it's impossible to tell _which_
// fields are missing. This is mainly relevant for contrived cases where a header is
// created at random, for example:
// * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
// post-London, so this is technically not valid. However, a tool like proptest would
// generate a block like this.
if let Some(ref requests_root) = self.requests_root {
requests_root.encode(out);
}
}
fn length(&self) -> usize {
let mut length = 0;
length += self.header_payload_length();
length += length_of_length(length);
length
}
}
impl Decodable for Header {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let rlp_head = alloy_rlp::Header::decode(buf)?;
if !rlp_head.list {
return Err(alloy_rlp::Error::UnexpectedString)
}
let started_len = buf.len();
let mut this = Self {
parent_hash: Decodable::decode(buf)?,
ommers_hash: Decodable::decode(buf)?,
beneficiary: Decodable::decode(buf)?,
state_root: Decodable::decode(buf)?,
transactions_root: Decodable::decode(buf)?,
receipts_root: Decodable::decode(buf)?,
logs_bloom: Decodable::decode(buf)?,
difficulty: Decodable::decode(buf)?,
number: u64::decode(buf)?,
gas_limit: u64::decode(buf)?,
gas_used: u64::decode(buf)?,
timestamp: Decodable::decode(buf)?,
extra_data: Decodable::decode(buf)?,
mix_hash: Decodable::decode(buf)?,
nonce: u64::from_be_bytes(B64::decode(buf)?.0),
base_fee_per_gas: None,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_root: None,
};
if started_len - buf.len() < rlp_head.payload_length {
this.base_fee_per_gas = Some(u64::decode(buf)?);
}
// Withdrawals root for post-shanghai headers
if started_len - buf.len() < rlp_head.payload_length {
this.withdrawals_root = Some(Decodable::decode(buf)?);
}
// Blob gas used and excess blob gas for post-cancun headers
if started_len - buf.len() < rlp_head.payload_length {
this.blob_gas_used = Some(u64::decode(buf)?);
}
if started_len - buf.len() < rlp_head.payload_length {
this.excess_blob_gas = Some(u64::decode(buf)?);
}
// Decode parent beacon block root.
if started_len - buf.len() < rlp_head.payload_length {
this.parent_beacon_block_root = Some(B256::decode(buf)?);
}
// Decode requests root.
//
// If new fields are added, the above pattern will need to
// be repeated and placeholders decoded. Otherwise, it's impossible to tell _which_
// fields are missing. This is mainly relevant for contrived cases where a header is
// created at random, for example:
// * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
// post-London, so this is technically not valid. However, a tool like proptest would
// generate a block like this.
if started_len - buf.len() < rlp_head.payload_length {
this.requests_root = Some(B256::decode(buf)?);
}
let consumed = started_len - buf.len();
if consumed != rlp_head.payload_length {
return Err(alloy_rlp::Error::ListLengthMismatch {
expected: rlp_head.payload_length,
got: consumed,
})
}
Ok(this)
}
}

View File

@ -0,0 +1,156 @@
use super::Header;
use alloy_eips::BlockNumHash;
use alloy_primitives::{keccak256, BlockHash};
#[cfg(any(test, feature = "test-utils"))]
use alloy_primitives::{BlockNumber, B256, U256};
use alloy_rlp::{Decodable, Encodable};
use bytes::BufMut;
use derive_more::{AsRef, Deref};
#[cfg(any(test, feature = "arbitrary"))]
use proptest::prelude::*;
use reth_codecs::{add_arbitrary_tests, main_codec, Compact};
use std::mem;
/// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want
/// to modify header.
#[main_codec(no_arbitrary)]
#[add_arbitrary_tests(rlp, compact)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref)]
pub struct SealedHeader {
/// Locked Header hash.
hash: BlockHash,
/// Locked Header fields.
#[as_ref]
#[deref]
header: Header,
}
impl SealedHeader {
/// Creates the sealed header with the corresponding block hash.
#[inline]
pub const fn new(header: Header, hash: BlockHash) -> Self {
Self { header, hash }
}
/// Returns the sealed Header fields.
#[inline]
pub const fn header(&self) -> &Header {
&self.header
}
/// Returns header/block hash.
#[inline]
pub const fn hash(&self) -> BlockHash {
self.hash
}
/// Extract raw header that can be modified.
pub fn unseal(self) -> Header {
self.header
}
/// This is the inverse of [`Header::seal_slow`] which returns the raw header and hash.
pub fn split(self) -> (Header, BlockHash) {
(self.header, self.hash)
}
/// Return the number hash tuple.
pub fn num_hash(&self) -> BlockNumHash {
BlockNumHash::new(self.number, self.hash)
}
/// Calculates a heuristic for the in-memory size of the [`SealedHeader`].
#[inline]
pub fn size(&self) -> usize {
self.header.size() + mem::size_of::<BlockHash>()
}
}
impl Default for SealedHeader {
fn default() -> Self {
Header::default().seal_slow()
}
}
impl Encodable for SealedHeader {
fn encode(&self, out: &mut dyn BufMut) {
self.header.encode(out);
}
}
impl Decodable for SealedHeader {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let b = &mut &**buf;
let started_len = buf.len();
// decode the header from temp buffer
let header = Header::decode(b)?;
// hash the consumed bytes, the rlp encoded header
let consumed = started_len - b.len();
let hash = keccak256(&buf[..consumed]);
// update original buffer
*buf = *b;
Ok(Self { header, hash })
}
}
#[cfg(any(test, feature = "test-utils"))]
impl SealedHeader {
/// Updates the block header.
pub fn set_header(&mut self, header: Header) {
self.header = header
}
/// Updates the block hash.
pub fn set_hash(&mut self, hash: BlockHash) {
self.hash = hash
}
/// Updates the parent block hash.
pub fn set_parent_hash(&mut self, hash: BlockHash) {
self.header.parent_hash = hash
}
/// Updates the block number.
pub fn set_block_number(&mut self, number: BlockNumber) {
self.header.number = number;
}
/// Updates the block state root.
pub fn set_state_root(&mut self, state_root: B256) {
self.header.state_root = state_root;
}
/// Updates the block difficulty.
pub fn set_difficulty(&mut self, difficulty: U256) {
self.header.difficulty = difficulty;
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for SealedHeader {
type Parameters = ();
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
// map valid header strategy by sealing
crate::test_utils::valid_header_strategy().prop_map(|header| header.seal_slow()).boxed()
}
type Strategy = proptest::strategy::BoxedStrategy<Self>;
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for SealedHeader {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let sealed_header = crate::test_utils::generate_valid_header(
u.arbitrary()?,
u.arbitrary()?,
u.arbitrary()?,
u.arbitrary()?,
u.arbitrary()?,
)
.seal_slow();
Ok(sealed_header)
}
}

View File

@ -0,0 +1,66 @@
//! Test utilities to generate random valid headers.
use crate::Header;
use alloy_primitives::B256;
use proptest::{arbitrary::any, prop_compose};
/// Generates a header which is valid __with respect to past and future forks__. This means, for
/// example, that if the withdrawals root is present, the base fee per gas is also present.
///
/// If blob gas used were present, then the excess blob gas and parent beacon block root are also
/// present. In this example, the withdrawals root would also be present.
///
/// This __does not, and should not guarantee__ that the header is valid with respect to __anything
/// else__.
pub const fn generate_valid_header(
mut header: Header,
eip_4844_active: bool,
blob_gas_used: u64,
excess_blob_gas: u64,
parent_beacon_block_root: B256,
) -> Header {
// EIP-1559 logic
if header.base_fee_per_gas.is_none() {
// If EIP-1559 is not active, clear related fields
header.withdrawals_root = None;
header.blob_gas_used = None;
header.excess_blob_gas = None;
header.parent_beacon_block_root = None;
} else if header.withdrawals_root.is_none() {
// If EIP-4895 is not active, clear related fields
header.blob_gas_used = None;
header.excess_blob_gas = None;
header.parent_beacon_block_root = None;
} else if eip_4844_active {
// Set fields based on EIP-4844 being active
header.blob_gas_used = Some(blob_gas_used);
header.excess_blob_gas = Some(excess_blob_gas);
header.parent_beacon_block_root = Some(parent_beacon_block_root);
} else {
// If EIP-4844 is not active, clear related fields
header.blob_gas_used = None;
header.excess_blob_gas = None;
header.parent_beacon_block_root = None;
}
// todo(onbjerg): adjust this for eip-7589
header.requests_root = None;
header
}
prop_compose! {
/// Generates a proptest strategy for constructing an instance of a header which is valid __with
/// respect to past and future forks__.
///
/// See docs for [generate_valid_header] for more information.
pub fn valid_header_strategy()(
header in any::<Header>(),
eip_4844_active in any::<bool>(),
blob_gas_used in any::<u64>(),
excess_blob_gas in any::<u64>(),
parent_beacon_block_root in any::<B256>()
) -> Header {
generate_valid_header(header, eip_4844_active, blob_gas_used, excess_blob_gas, parent_beacon_block_root)
}
}

View File

@ -10,6 +10,15 @@
#![allow(unknown_lints, non_local_definitions)] #![allow(unknown_lints, non_local_definitions)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#[cfg(feature = "alloy-compat")]
mod alloy_compat;
/// Minimal account /// Minimal account
pub mod account; pub mod account;
pub use account::Account; pub use account::Account;
/// Common header types
pub mod header;
#[cfg(any(test, feature = "arbitrary", feature = "test-utils"))]
pub use header::test_utils;
pub use header::{Header, HeaderError, SealedHeader};

View File

@ -117,8 +117,11 @@ optimism = [
"reth-ethereum-forks/optimism", "reth-ethereum-forks/optimism",
"revm/optimism", "revm/optimism",
] ]
alloy-compat = ["alloy-rpc-types"] alloy-compat = [
test-utils = [] "reth-primitives-traits/alloy-compat",
"alloy-rpc-types",
]
test-utils = ["reth-primitives-traits/test-utils"]
[[bench]] [[bench]]
name = "recover_ecdsa_crit" name = "recover_ecdsa_crit"

View File

@ -1,9 +1,9 @@
//! Common conversions from alloy types. //! Common conversions from alloy types.
use crate::{ use crate::{
constants::EMPTY_TRANSACTIONS, transaction::extract_chain_id, Block, Header, Signature, constants::EMPTY_TRANSACTIONS, transaction::extract_chain_id, Block, Signature, Transaction,
Transaction, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy,
TxLegacy, TxType, TxType,
}; };
use alloy_primitives::TxKind; use alloy_primitives::TxKind;
use alloy_rlp::Error as RlpError; use alloy_rlp::Error as RlpError;
@ -61,54 +61,6 @@ impl TryFrom<alloy_rpc_types::Block> for Block {
} }
} }
impl TryFrom<alloy_rpc_types::Header> for Header {
type Error = alloy_rpc_types::ConversionError;
fn try_from(header: alloy_rpc_types::Header) -> Result<Self, Self::Error> {
use alloy_rpc_types::ConversionError;
Ok(Self {
base_fee_per_gas: header
.base_fee_per_gas
.map(|base_fee_per_gas| {
base_fee_per_gas.try_into().map_err(ConversionError::BaseFeePerGasConversion)
})
.transpose()?,
beneficiary: header.miner,
blob_gas_used: header
.blob_gas_used
.map(|blob_gas_used| {
blob_gas_used.try_into().map_err(ConversionError::BlobGasUsedConversion)
})
.transpose()?,
difficulty: header.difficulty,
excess_blob_gas: header
.excess_blob_gas
.map(|excess_blob_gas| {
excess_blob_gas.try_into().map_err(ConversionError::ExcessBlobGasConversion)
})
.transpose()?,
extra_data: header.extra_data,
gas_limit: header.gas_limit.try_into().map_err(ConversionError::GasLimitConversion)?,
gas_used: header.gas_used.try_into().map_err(ConversionError::GasUsedConversion)?,
logs_bloom: header.logs_bloom,
mix_hash: header.mix_hash.unwrap_or_default(),
nonce: u64::from_be_bytes(header.nonce.unwrap_or_default().0),
number: header.number.ok_or(ConversionError::MissingBlockNumber)?,
ommers_hash: header.uncles_hash,
parent_beacon_block_root: header.parent_beacon_block_root,
parent_hash: header.parent_hash,
receipts_root: header.receipts_root,
state_root: header.state_root,
timestamp: header.timestamp,
transactions_root: header.transactions_root,
withdrawals_root: header.withdrawals_root,
// TODO: requests_root: header.requests_root,
requests_root: None,
})
}
}
impl TryFrom<alloy_rpc_types::Transaction> for Transaction { impl TryFrom<alloy_rpc_types::Transaction> for Transaction {
type Error = alloy_rpc_types::ConversionError; type Error = alloy_rpc_types::ConversionError;

View File

@ -2,16 +2,17 @@ use crate::{
Address, Bytes, GotExpected, Header, Requests, SealedHeader, TransactionSigned, Address, Bytes, GotExpected, Header, Requests, SealedHeader, TransactionSigned,
TransactionSignedEcRecovered, Withdrawals, B256, TransactionSignedEcRecovered, Withdrawals, B256,
}; };
use alloy_rlp::{RlpDecodable, RlpEncodable};
use derive_more::{Deref, DerefMut};
#[cfg(any(test, feature = "arbitrary"))]
use proptest::prelude::{any, prop_compose};
use reth_codecs::derive_arbitrary;
use serde::{Deserialize, Serialize};
pub use alloy_eips::eip1898::{ pub use alloy_eips::eip1898::{
BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash, BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash,
}; };
use alloy_rlp::{RlpDecodable, RlpEncodable};
use derive_more::{Deref, DerefMut};
#[cfg(any(test, feature = "arbitrary"))]
use proptest::prelude::prop_compose;
use reth_codecs::derive_arbitrary;
#[cfg(any(test, feature = "arbitrary"))]
pub use reth_primitives_traits::test_utils::{generate_valid_header, valid_header_strategy};
use serde::{Deserialize, Serialize};
// HACK(onbjerg): we need this to always set `requests` to `None` since we might otherwise generate // HACK(onbjerg): we need this to always set `requests` to `None` since we might otherwise generate
// a block with `None` withdrawals and `Some` requests, in which case we end up trying to decode the // a block with `None` withdrawals and `Some` requests, in which case we end up trying to decode the
@ -592,69 +593,6 @@ impl From<Block> for BlockBody {
} }
} }
/// Generates a header which is valid __with respect to past and future forks__. This means, for
/// example, that if the withdrawals root is present, the base fee per gas is also present.
///
/// If blob gas used were present, then the excess blob gas and parent beacon block root are also
/// present. In this example, the withdrawals root would also be present.
///
/// This __does not, and should not guarantee__ that the header is valid with respect to __anything
/// else__.
#[cfg(any(test, feature = "arbitrary"))]
pub const fn generate_valid_header(
mut header: Header,
eip_4844_active: bool,
blob_gas_used: u64,
excess_blob_gas: u64,
parent_beacon_block_root: B256,
) -> Header {
// EIP-1559 logic
if header.base_fee_per_gas.is_none() {
// If EIP-1559 is not active, clear related fields
header.withdrawals_root = None;
header.blob_gas_used = None;
header.excess_blob_gas = None;
header.parent_beacon_block_root = None;
} else if header.withdrawals_root.is_none() {
// If EIP-4895 is not active, clear related fields
header.blob_gas_used = None;
header.excess_blob_gas = None;
header.parent_beacon_block_root = None;
} else if eip_4844_active {
// Set fields based on EIP-4844 being active
header.blob_gas_used = Some(blob_gas_used);
header.excess_blob_gas = Some(excess_blob_gas);
header.parent_beacon_block_root = Some(parent_beacon_block_root);
} else {
// If EIP-4844 is not active, clear related fields
header.blob_gas_used = None;
header.excess_blob_gas = None;
header.parent_beacon_block_root = None;
}
// todo(onbjerg): adjust this for eip-7589
header.requests_root = None;
header
}
#[cfg(any(test, feature = "arbitrary"))]
prop_compose! {
/// Generates a proptest strategy for constructing an instance of a header which is valid __with
/// respect to past and future forks__.
///
/// See docs for [generate_valid_header] for more information.
pub fn valid_header_strategy()(
header in any::<Header>(),
eip_4844_active in any::<bool>(),
blob_gas_used in any::<u64>(),
excess_blob_gas in any::<u64>(),
parent_beacon_block_root in any::<B256>()
) -> Header {
generate_valid_header(header, eip_4844_active, blob_gas_used, excess_blob_gas, parent_beacon_block_root)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{BlockNumberOrTag::*, *}; use super::{BlockNumberOrTag::*, *};

View File

@ -1,665 +1,11 @@
#[cfg(any(test, feature = "arbitrary"))] //! Header types.
use crate::block::{generate_valid_header, valid_header_strategy};
use crate::{ use alloy_rlp::{Decodable, Encodable};
basefee::calc_next_block_base_fee,
constants::{ALLOWED_FUTURE_BLOCK_TIME_SECONDS, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH},
eip4844::{calc_blob_gasprice, calculate_excess_blob_gas},
keccak256, Address, BaseFeeParams, BlockHash, BlockNumHash, BlockNumber, Bloom, Bytes, B256,
B64, U256,
};
use alloy_rlp::{length_of_length, Decodable, Encodable};
use bytes::BufMut; use bytes::BufMut;
use derive_more::{AsRef, Deref}; use reth_codecs::derive_arbitrary;
#[cfg(any(test, feature = "arbitrary"))]
use proptest::prelude::*;
use reth_codecs::{add_arbitrary_tests, derive_arbitrary, main_codec, Compact};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::mem;
/// Errors that can occur during header sanity checks. pub use reth_primitives_traits::{Header, HeaderError, SealedHeader};
#[derive(Debug, PartialEq, Eq)]
pub enum HeaderError {
/// Represents an error when the block difficulty is too large.
LargeDifficulty,
/// Represents an error when the block extradata is too large.
LargeExtraData,
}
/// Block header
#[main_codec]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Header {
/// The Keccak 256-bit hash of the parent
/// blocks header, in its entirety; formally Hp.
pub parent_hash: B256,
/// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho.
pub ommers_hash: B256,
/// The 160-bit address to which all fees collected from the successful mining of this block
/// be transferred; formally Hc.
pub beneficiary: Address,
/// The Keccak 256-bit hash of the root node of the state trie, after all transactions are
/// executed and finalisations applied; formally Hr.
pub state_root: B256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with each
/// transaction in the transactions list portion of the block; formally Ht.
pub transactions_root: B256,
/// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts
/// of each transaction in the transactions list portion of the block; formally He.
pub receipts_root: B256,
/// The Keccak 256-bit hash of the withdrawals list portion of this block.
///
/// See [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895).
pub withdrawals_root: Option<B256>,
/// The Bloom filter composed from indexable information (logger address and log topics)
/// contained in each log entry from the receipt of each transaction in the transactions list;
/// formally Hb.
pub logs_bloom: Bloom,
/// A scalar value corresponding to the difficulty level of this block. This can be calculated
/// from the previous blocks difficulty level and the timestamp; formally Hd.
pub difficulty: U256,
/// A scalar value equal to the number of ancestor blocks. The genesis block has a number of
/// zero; formally Hi.
pub number: BlockNumber,
/// A scalar value equal to the current limit of gas expenditure per block; formally Hl.
pub gas_limit: u64,
/// A scalar value equal to the total gas used in transactions in this block; formally Hg.
pub gas_used: u64,
/// A scalar value equal to the reasonable output of Unixs time() at this blocks inception;
/// formally Hs.
pub timestamp: u64,
/// A 256-bit hash which, combined with the
/// nonce, proves that a sufficient amount of computation has been carried out on this block;
/// formally Hm.
pub mix_hash: B256,
/// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of
/// computation has been carried out on this block; formally Hn.
pub nonce: u64,
/// A scalar representing EIP1559 base fee which can move up or down each block according
/// to a formula which is a function of gas used in parent block and gas target
/// (block gas limit divided by elasticity multiplier) of parent block.
/// The algorithm results in the base fee per gas increasing when blocks are
/// above the gas target, and decreasing when blocks are below the gas target. The base fee per
/// gas is burned.
pub base_fee_per_gas: Option<u64>,
/// The total amount of blob gas consumed by the transactions within the block, added in
/// EIP-4844.
pub blob_gas_used: Option<u64>,
/// A running total of blob gas consumed in excess of the target, prior to the block. Blocks
/// with above-target blob gas consumption increase this value, blocks with below-target blob
/// gas consumption decrease it (bounded at 0). This was added in EIP-4844.
pub excess_blob_gas: Option<u64>,
/// The hash of the parent beacon block's root is included in execution blocks, as proposed by
/// EIP-4788.
///
/// This enables trust-minimized access to consensus state, supporting staking pools, bridges,
/// and more.
///
/// The beacon roots contract handles root storage, enhancing Ethereum's functionalities.
pub parent_beacon_block_root: Option<B256>,
/// The Keccak 256-bit hash of the root node of the trie structure populated with each
/// [EIP-7685] request in the block body.
///
/// [EIP-7685]: https://eips.ethereum.org/EIPS/eip-7685
pub requests_root: Option<B256>,
/// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or
/// fewer; formally Hx.
pub extra_data: Bytes,
}
impl AsRef<Self> for Header {
fn as_ref(&self) -> &Self {
self
}
}
impl Default for Header {
fn default() -> Self {
Self {
parent_hash: Default::default(),
ommers_hash: EMPTY_OMMER_ROOT_HASH,
beneficiary: Default::default(),
state_root: EMPTY_ROOT_HASH,
transactions_root: EMPTY_ROOT_HASH,
receipts_root: EMPTY_ROOT_HASH,
logs_bloom: Default::default(),
difficulty: Default::default(),
number: 0,
gas_limit: 0,
gas_used: 0,
timestamp: 0,
extra_data: Default::default(),
mix_hash: Default::default(),
nonce: 0,
base_fee_per_gas: None,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_root: None,
}
}
}
impl Header {
/// Checks if the block's difficulty is set to zero, indicating a Proof-of-Stake header.
///
/// This function is linked to EIP-3675, proposing the consensus upgrade to Proof-of-Stake:
/// [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#replacing-difficulty-with-0)
///
/// Verifies whether, as per the EIP, the block's difficulty is updated to zero,
/// signifying the transition to a Proof-of-Stake mechanism.
///
/// Returns `true` if the block's difficulty matches the constant zero set by the EIP.
pub fn is_zero_difficulty(&self) -> bool {
self.difficulty.is_zero()
}
/// Performs a sanity check on the extradata field of the header.
///
/// # Errors
///
/// Returns an error if the extradata size is larger than 100 KB.
pub fn ensure_extradata_valid(&self) -> Result<(), HeaderError> {
if self.extra_data.len() > 100 * 1024 {
return Err(HeaderError::LargeExtraData)
}
Ok(())
}
/// Performs a sanity check on the block difficulty field of the header.
///
/// # Errors
///
/// Returns an error if the block difficulty exceeds 80 bits.
pub fn ensure_difficulty_valid(&self) -> Result<(), HeaderError> {
if self.difficulty.bit_len() > 80 {
return Err(HeaderError::LargeDifficulty)
}
Ok(())
}
/// Performs combined sanity checks on multiple header fields.
///
/// This method combines checks for block difficulty and extradata sizes.
///
/// # Errors
///
/// Returns an error if either the block difficulty exceeds 80 bits
/// or if the extradata size is larger than 100 KB.
pub fn ensure_well_formed(&self) -> Result<(), HeaderError> {
self.ensure_difficulty_valid()?;
self.ensure_extradata_valid()?;
Ok(())
}
/// Checks if the block's timestamp is in the past compared to the parent block's timestamp.
///
/// Note: This check is relevant only pre-merge.
pub const fn is_timestamp_in_past(&self, parent_timestamp: u64) -> bool {
self.timestamp <= parent_timestamp
}
/// Checks if the block's timestamp is in the future based on the present timestamp.
///
/// Clock can drift but this can be consensus issue.
///
/// Note: This check is relevant only pre-merge.
pub const fn exceeds_allowed_future_timestamp(&self, present_timestamp: u64) -> bool {
self.timestamp > present_timestamp + ALLOWED_FUTURE_BLOCK_TIME_SECONDS
}
/// Returns the parent block's number and hash
pub const fn parent_num_hash(&self) -> BlockNumHash {
BlockNumHash { number: self.number.saturating_sub(1), hash: self.parent_hash }
}
/// Heavy function that will calculate hash of data and will *not* save the change to metadata.
/// Use [`Header::seal`], [`SealedHeader`] and unlock if you need hash to be persistent.
pub fn hash_slow(&self) -> B256 {
keccak256(alloy_rlp::encode(self))
}
/// Checks if the header is empty - has no transactions and no ommers
pub fn is_empty(&self) -> bool {
self.transaction_root_is_empty() &&
self.ommers_hash_is_empty() &&
self.withdrawals_root.map_or(true, |root| root == EMPTY_ROOT_HASH)
}
/// Check if the ommers hash equals to empty hash list.
pub fn ommers_hash_is_empty(&self) -> bool {
self.ommers_hash == EMPTY_OMMER_ROOT_HASH
}
/// Check if the transaction root equals to empty root.
pub fn transaction_root_is_empty(&self) -> bool {
self.transactions_root == EMPTY_ROOT_HASH
}
/// Returns the blob fee for _this_ block according to the EIP-4844 spec.
///
/// Returns `None` if `excess_blob_gas` is None
pub fn blob_fee(&self) -> Option<u128> {
self.excess_blob_gas.map(calc_blob_gasprice)
}
/// Returns the blob fee for the next block according to the EIP-4844 spec.
///
/// Returns `None` if `excess_blob_gas` is None.
///
/// See also [`Self::next_block_excess_blob_gas`]
pub fn next_block_blob_fee(&self) -> Option<u128> {
self.next_block_excess_blob_gas().map(calc_blob_gasprice)
}
/// Calculate base fee for next block according to the EIP-1559 spec.
///
/// Returns a `None` if no base fee is set, no EIP-1559 support
pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
Some(calc_next_block_base_fee(
self.gas_used as u128,
self.gas_limit as u128,
self.base_fee_per_gas? as u128,
base_fee_params,
) as u64)
}
/// Calculate excess blob gas for the next block according to the EIP-4844 spec.
///
/// Returns a `None` if no excess blob gas is set, no EIP-4844 support
pub fn next_block_excess_blob_gas(&self) -> Option<u64> {
Some(calculate_excess_blob_gas(self.excess_blob_gas?, self.blob_gas_used?))
}
/// Seal the header with a known hash.
///
/// WARNING: This method does not perform validation whether the hash is correct.
#[inline]
pub const fn seal(self, hash: B256) -> SealedHeader {
SealedHeader { header: self, hash }
}
/// Calculate hash and seal the Header so that it can't be changed.
#[inline]
pub fn seal_slow(self) -> SealedHeader {
let hash = self.hash_slow();
self.seal(hash)
}
/// Calculate a heuristic for the in-memory size of the [Header].
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<B256>() + // parent hash
mem::size_of::<B256>() + // ommers hash
mem::size_of::<Address>() + // beneficiary
mem::size_of::<B256>() + // state root
mem::size_of::<B256>() + // transactions root
mem::size_of::<B256>() + // receipts root
mem::size_of::<Option<B256>>() + // withdrawals root
mem::size_of::<Bloom>() + // logs bloom
mem::size_of::<U256>() + // difficulty
mem::size_of::<BlockNumber>() + // number
mem::size_of::<u64>() + // gas limit
mem::size_of::<u64>() + // gas used
mem::size_of::<u64>() + // timestamp
mem::size_of::<B256>() + // mix hash
mem::size_of::<u64>() + // nonce
mem::size_of::<Option<u64>>() + // base fee per gas
mem::size_of::<Option<u64>>() + // blob gas used
mem::size_of::<Option<u64>>() + // excess blob gas
mem::size_of::<Option<B256>>() + // parent beacon block root
self.extra_data.len() // extra data
}
fn header_payload_length(&self) -> usize {
let mut length = 0;
length += self.parent_hash.length(); // Hash of the previous block.
length += self.ommers_hash.length(); // Hash of uncle blocks.
length += self.beneficiary.length(); // Address that receives rewards.
length += self.state_root.length(); // Root hash of the state object.
length += self.transactions_root.length(); // Root hash of transactions in the block.
length += self.receipts_root.length(); // Hash of transaction receipts.
length += self.logs_bloom.length(); // Data structure containing event logs.
length += self.difficulty.length(); // Difficulty value of the block.
length += U256::from(self.number).length(); // Block number.
length += U256::from(self.gas_limit).length(); // Maximum gas allowed.
length += U256::from(self.gas_used).length(); // Actual gas used.
length += self.timestamp.length(); // Block timestamp.
length += self.extra_data.length(); // Additional arbitrary data.
length += self.mix_hash.length(); // Hash used for mining.
length += B64::new(self.nonce.to_be_bytes()).length(); // Nonce for mining.
if let Some(base_fee) = self.base_fee_per_gas {
// Adding base fee length if it exists.
length += U256::from(base_fee).length();
}
if let Some(root) = self.withdrawals_root {
// Adding withdrawals_root length if it exists.
length += root.length();
}
if let Some(blob_gas_used) = self.blob_gas_used {
// Adding blob_gas_used length if it exists.
length += U256::from(blob_gas_used).length();
}
if let Some(excess_blob_gas) = self.excess_blob_gas {
// Adding excess_blob_gas length if it exists.
length += U256::from(excess_blob_gas).length();
}
if let Some(parent_beacon_block_root) = self.parent_beacon_block_root {
length += parent_beacon_block_root.length();
}
if let Some(requests_root) = self.requests_root {
length += requests_root.length();
}
length
}
}
impl Encodable for Header {
fn encode(&self, out: &mut dyn BufMut) {
// Create a header indicating the encoded content is a list with the payload length computed
// from the header's payload calculation function.
let list_header =
alloy_rlp::Header { list: true, payload_length: self.header_payload_length() };
list_header.encode(out);
// Encode each header field sequentially
self.parent_hash.encode(out); // Encode parent hash.
self.ommers_hash.encode(out); // Encode ommer's hash.
self.beneficiary.encode(out); // Encode beneficiary.
self.state_root.encode(out); // Encode state root.
self.transactions_root.encode(out); // Encode transactions root.
self.receipts_root.encode(out); // Encode receipts root.
self.logs_bloom.encode(out); // Encode logs bloom.
self.difficulty.encode(out); // Encode difficulty.
U256::from(self.number).encode(out); // Encode block number.
U256::from(self.gas_limit).encode(out); // Encode gas limit.
U256::from(self.gas_used).encode(out); // Encode gas used.
self.timestamp.encode(out); // Encode timestamp.
self.extra_data.encode(out); // Encode extra data.
self.mix_hash.encode(out); // Encode mix hash.
B64::new(self.nonce.to_be_bytes()).encode(out); // Encode nonce.
// Encode base fee. Put empty list if base fee is missing,
// but withdrawals root is present.
if let Some(ref base_fee) = self.base_fee_per_gas {
U256::from(*base_fee).encode(out);
}
// Encode withdrawals root. Put empty string if withdrawals root is missing,
// but blob gas used is present.
if let Some(ref root) = self.withdrawals_root {
root.encode(out);
}
// Encode blob gas used. Put empty list if blob gas used is missing,
// but excess blob gas is present.
if let Some(ref blob_gas_used) = self.blob_gas_used {
U256::from(*blob_gas_used).encode(out);
}
// Encode excess blob gas. Put empty list if excess blob gas is missing,
// but parent beacon block root is present.
if let Some(ref excess_blob_gas) = self.excess_blob_gas {
U256::from(*excess_blob_gas).encode(out);
}
// Encode parent beacon block root.
if let Some(ref parent_beacon_block_root) = self.parent_beacon_block_root {
parent_beacon_block_root.encode(out);
}
// Encode EIP-7685 requests root
//
// If new fields are added, the above pattern will need to
// be repeated and placeholders added. Otherwise, it's impossible to tell _which_
// fields are missing. This is mainly relevant for contrived cases where a header is
// created at random, for example:
// * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
// post-London, so this is technically not valid. However, a tool like proptest would
// generate a block like this.
if let Some(ref requests_root) = self.requests_root {
requests_root.encode(out);
}
}
fn length(&self) -> usize {
let mut length = 0;
length += self.header_payload_length();
length += length_of_length(length);
length
}
}
impl Decodable for Header {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let rlp_head = alloy_rlp::Header::decode(buf)?;
if !rlp_head.list {
return Err(alloy_rlp::Error::UnexpectedString)
}
let started_len = buf.len();
let mut this = Self {
parent_hash: Decodable::decode(buf)?,
ommers_hash: Decodable::decode(buf)?,
beneficiary: Decodable::decode(buf)?,
state_root: Decodable::decode(buf)?,
transactions_root: Decodable::decode(buf)?,
receipts_root: Decodable::decode(buf)?,
logs_bloom: Decodable::decode(buf)?,
difficulty: Decodable::decode(buf)?,
number: u64::decode(buf)?,
gas_limit: u64::decode(buf)?,
gas_used: u64::decode(buf)?,
timestamp: Decodable::decode(buf)?,
extra_data: Decodable::decode(buf)?,
mix_hash: Decodable::decode(buf)?,
nonce: u64::from_be_bytes(B64::decode(buf)?.0),
base_fee_per_gas: None,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_root: None,
};
if started_len - buf.len() < rlp_head.payload_length {
this.base_fee_per_gas = Some(u64::decode(buf)?);
}
// Withdrawals root for post-shanghai headers
if started_len - buf.len() < rlp_head.payload_length {
this.withdrawals_root = Some(Decodable::decode(buf)?);
}
// Blob gas used and excess blob gas for post-cancun headers
if started_len - buf.len() < rlp_head.payload_length {
this.blob_gas_used = Some(u64::decode(buf)?);
}
if started_len - buf.len() < rlp_head.payload_length {
this.excess_blob_gas = Some(u64::decode(buf)?);
}
// Decode parent beacon block root.
if started_len - buf.len() < rlp_head.payload_length {
this.parent_beacon_block_root = Some(B256::decode(buf)?);
}
// Decode requests root.
//
// If new fields are added, the above pattern will need to
// be repeated and placeholders decoded. Otherwise, it's impossible to tell _which_
// fields are missing. This is mainly relevant for contrived cases where a header is
// created at random, for example:
// * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
// post-London, so this is technically not valid. However, a tool like proptest would
// generate a block like this.
if started_len - buf.len() < rlp_head.payload_length {
this.requests_root = Some(B256::decode(buf)?);
}
let consumed = started_len - buf.len();
if consumed != rlp_head.payload_length {
return Err(alloy_rlp::Error::ListLengthMismatch {
expected: rlp_head.payload_length,
got: consumed,
})
}
Ok(this)
}
}
/// A [`Header`] that is sealed at a precalculated hash, use [`SealedHeader::unseal()`] if you want
/// to modify header.
#[main_codec(no_arbitrary)]
#[add_arbitrary_tests(rlp, compact)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref)]
pub struct SealedHeader {
/// Locked Header hash.
hash: BlockHash,
/// Locked Header fields.
#[as_ref]
#[deref]
header: Header,
}
impl SealedHeader {
/// Creates the sealed header with the corresponding block hash.
#[inline]
pub const fn new(header: Header, hash: BlockHash) -> Self {
Self { header, hash }
}
/// Returns the sealed Header fields.
#[inline]
pub const fn header(&self) -> &Header {
&self.header
}
/// Returns header/block hash.
#[inline]
pub const fn hash(&self) -> BlockHash {
self.hash
}
/// Updates the block header.
#[cfg(any(test, feature = "test-utils"))]
pub fn set_header(&mut self, header: Header) {
self.header = header
}
/// Updates the block hash.
#[cfg(any(test, feature = "test-utils"))]
pub fn set_hash(&mut self, hash: BlockHash) {
self.hash = hash
}
/// Updates the parent block hash.
#[cfg(any(test, feature = "test-utils"))]
pub fn set_parent_hash(&mut self, hash: BlockHash) {
self.header.parent_hash = hash
}
/// Updates the block number.
#[cfg(any(test, feature = "test-utils"))]
pub fn set_block_number(&mut self, number: BlockNumber) {
self.header.number = number;
}
/// Updates the block state root.
#[cfg(any(test, feature = "test-utils"))]
pub fn set_state_root(&mut self, state_root: B256) {
self.header.state_root = state_root;
}
/// Updates the block difficulty.
#[cfg(any(test, feature = "test-utils"))]
pub fn set_difficulty(&mut self, difficulty: U256) {
self.header.difficulty = difficulty;
}
/// Extract raw header that can be modified.
pub fn unseal(self) -> Header {
self.header
}
/// This is the inverse of [`Header::seal_slow`] which returns the raw header and hash.
pub fn split(self) -> (Header, BlockHash) {
(self.header, self.hash)
}
/// Return the number hash tuple.
pub fn num_hash(&self) -> BlockNumHash {
BlockNumHash::new(self.number, self.hash)
}
/// Calculates a heuristic for the in-memory size of the [`SealedHeader`].
#[inline]
pub fn size(&self) -> usize {
self.header.size() + mem::size_of::<BlockHash>()
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for SealedHeader {
type Parameters = ();
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
// map valid header strategy by sealing
valid_header_strategy().prop_map(|header| header.seal_slow()).boxed()
}
type Strategy = proptest::strategy::BoxedStrategy<Self>;
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for SealedHeader {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let sealed_header = generate_valid_header(
u.arbitrary()?,
u.arbitrary()?,
u.arbitrary()?,
u.arbitrary()?,
u.arbitrary()?,
)
.seal_slow();
Ok(sealed_header)
}
}
impl Default for SealedHeader {
fn default() -> Self {
Header::default().seal_slow()
}
}
impl Encodable for SealedHeader {
fn encode(&self, out: &mut dyn BufMut) {
self.header.encode(out);
}
}
impl Decodable for SealedHeader {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let b = &mut &**buf;
let started_len = buf.len();
// decode the header from temp buffer
let header = Header::decode(b)?;
// hash the consumed bytes, the rlp encoded header
let consumed = started_len - b.len();
let hash = keccak256(&buf[..consumed]);
// update original buffer
*buf = *b;
Ok(Self { header, hash })
}
}
/// Represents the direction for a headers request depending on the `reverse` field of the request. /// Represents the direction for a headers request depending on the `reverse` field of the request.
/// > The response must contain a number of block headers, of rising number when reverse is 0, /// > The response must contain a number of block headers, of rising number when reverse is 0,
@ -741,8 +87,10 @@ impl From<HeadersDirection> for bool {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Bytes, Decodable, Encodable, Header, B256}; use crate::{
use crate::{address, b256, bloom, bytes, hex, Address, HeadersDirection, U256}; address, b256, bloom, bytes, hex, Address, Bytes, Header, HeadersDirection, B256, U256,
};
use alloy_rlp::{Decodable, Encodable};
use std::str::FromStr; use std::str::FromStr;
// Test vector from: https://eips.ethereum.org/EIPS/eip-2481 // Test vector from: https://eips.ethereum.org/EIPS/eip-2481

View File

@ -30,7 +30,7 @@ pub mod constants;
pub mod eip4844; pub mod eip4844;
mod error; mod error;
pub mod genesis; pub mod genesis;
mod header; pub mod header;
mod integer_list; mod integer_list;
mod log; mod log;
mod net; mod net;