diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 433c4f44ea0..c816e4c011b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: rust: - "stable" - "nightly" - - "1.79" # MSRV + - "1.81" # MSRV flags: # No features - "--no-default-features" @@ -34,7 +34,7 @@ jobs: - "--all-features" exclude: # All features on MSRV - - rust: "1.79" # MSRV + - rust: "1.81" # MSRV flags: "--all-features" steps: - uses: actions/checkout@v4 @@ -53,14 +53,14 @@ jobs: cache-on-failure: true # Only run tests on latest stable and above - name: Install cargo-nextest - if: ${{ matrix.rust != '1.79' }} # MSRV + if: ${{ matrix.rust != '1.81' }} # MSRV uses: taiki-e/install-action@nextest - name: build - if: ${{ matrix.rust == '1.79' }} # MSRV + if: ${{ matrix.rust == '1.81' }} # MSRV run: cargo build --workspace ${{ matrix.flags }} - name: test shell: bash - if: ${{ matrix.rust != '1.79' }} # MSRV + if: ${{ matrix.rust != '1.81' }} # MSRV run: cargo nextest run --workspace ${{ matrix.flags }} doctest: diff --git a/Cargo.toml b/Cargo.toml index 65ab7623c32..cb798c194ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [workspace.package] version = "0.5.4" edition = "2021" -rust-version = "1.79" +rust-version = "1.81" authors = ["Alloy Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/alloy-rs/alloy" diff --git a/README.md b/README.md index c3eeecec195..c360b2111eb 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ When updating this, also update: - .github/workflows/ci.yml --> -The current MSRV (minimum supported rust version) is 1.79. +The current MSRV (minimum supported rust version) is 1.81. Alloy will keep a rolling MSRV policy of **at least** two versions behind the latest stable release (so if the latest stable release is 1.58, we would diff --git a/clippy.toml b/clippy.toml index f1acf4b1122..8c0bc009eb0 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.79" +msrv = "1.81" diff --git a/crates/consensus/Cargo.toml b/crates/consensus/Cargo.toml index 03d4c01f163..2e591765ae8 100644 --- a/crates/consensus/Cargo.toml +++ b/crates/consensus/Cargo.toml @@ -39,7 +39,8 @@ derive_more = { workspace = true, features = [ "from", "deref", "deref_mut", - "into_iterator" + "into_iterator", + "error" ], default-features = false } auto_impl.workspace = true diff --git a/crates/consensus/src/header.rs b/crates/consensus/src/header.rs index fa4f1cf6baa..1fd3d0fb59c 100644 --- a/crates/consensus/src/header.rs +++ b/crates/consensus/src/header.rs @@ -12,6 +12,35 @@ use alloy_primitives::{ use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable}; use core::mem; +/// Consensus Errors +#[derive(Debug, PartialEq, Eq, Clone, derive_more::Display, derive_more::Error)] +pub enum ConsensusError { + /// Error when blob gas used is missing. + #[display("missing blob gas used")] + BlobGasUsedMissing, + + /// Error when excess blob gas is missing. + #[display("missing excess blob gas")] + ExcessBlobGasMissing, + + /// Error when there is an invalid excess blob gas. + #[display( + "invalid excess blob gas, got: {got}, expected: {expected}; \ + parent excess blob gas: {parent_excess_blob_gas}, \ + parent blob gas used: {parent_blob_gas_used}" + )] + ExcessBlobGasDiff { + /// Expected excess blob gas. + expected: u64, + /// Actual excess blob gas. + got: u64, + /// The parent excess blob gas. + parent_excess_blob_gas: u64, + /// The parent blob gas used. + parent_blob_gas_used: u64, + }, +} + /// Ethereum Block header #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -346,6 +375,50 @@ impl Header { pub const fn seal(self, hash: B256) -> Sealed { Sealed::new_unchecked(self, hash) } + + /// Validates that the EIP-4844 fields in the current block header are correct compared to the + /// parent block header. + /// + /// Specifically, this function checks two things: + /// 1. The current block header contains the `blob_gas_used` and `excess_blob_gas` fields. + /// 2. The `excess_blob_gas` field matches the expected value calculated from the parent header + /// fields. + pub fn validate_against_parent_4844(&self, parent: &Self) -> Result<(), ConsensusError> { + // From [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension): + // + // > For the first post-fork block, both parent.blob_gas_used and parent.excess_blob_gas + // > are evaluated as 0. + // + // This means in the first post-fork block, calc_excess_blob_gas will return 0. + let parent_blob_gas_used = parent.blob_gas_used.unwrap_or(0); + let parent_excess_blob_gas = parent.excess_blob_gas.unwrap_or(0); + + // Ensure `blob_gas_used` exists in the current block header. + // Return an error if it is missing. + if self.blob_gas_used.is_none() { + return Err(ConsensusError::BlobGasUsedMissing); + } + + // Retrieve the `excess_blob_gas` field from the current block header. + let excess_blob_gas = self.excess_blob_gas.ok_or(ConsensusError::ExcessBlobGasMissing)?; + + // Calculate the expected `excess_blob_gas` for the current block. + let expected_excess_blob_gas = + calc_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used); + + // Check if the calculated `excess_blob_gas` matches the actual value in the header. + // If they are different, return an error with both values for comparison. + if expected_excess_blob_gas != excess_blob_gas { + return Err(ConsensusError::ExcessBlobGasDiff { + got: excess_blob_gas, + expected: expected_excess_blob_gas, + parent_excess_blob_gas, + parent_blob_gas_used, + }); + } + + Ok(()) + } } impl Encodable for Header { diff --git a/crates/consensus/src/lib.rs b/crates/consensus/src/lib.rs index 491393c49ac..93df1ca8694 100644 --- a/crates/consensus/src/lib.rs +++ b/crates/consensus/src/lib.rs @@ -22,7 +22,7 @@ mod encodable_signature; pub use encodable_signature::EncodableSignature; mod header; -pub use header::{BlockHeader, Header}; +pub use header::{BlockHeader, ConsensusError, Header}; mod receipt; pub use receipt::{