mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
chore: move Header and SealedHeader to reth-primitives-traits (#8831)
This commit is contained in:
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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 = [
|
||||||
|
|||||||
@ -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"]
|
||||||
48
crates/primitives-traits/src/alloy_compat.rs
Normal file
48
crates/primitives-traits/src/alloy_compat.rs
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
8
crates/primitives-traits/src/header/error.rs
Normal file
8
crates/primitives-traits/src/header/error.rs
Normal 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,
|
||||||
|
}
|
||||||
509
crates/primitives-traits/src/header/mod.rs
Normal file
509
crates/primitives-traits/src/header/mod.rs
Normal 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
|
||||||
|
/// block’s 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 block’s 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 Unix’s time() at this block’s 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
156
crates/primitives-traits/src/header/sealed.rs
Normal file
156
crates/primitives-traits/src/header/sealed.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
66
crates/primitives-traits/src/header/test_utils.rs
Normal file
66
crates/primitives-traits/src/header/test_utils.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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};
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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::*, *};
|
||||||
|
|||||||
@ -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
|
|
||||||
/// block’s 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 block’s 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 Unix’s time() at this block’s 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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user