Skip to content

Commit

Permalink
feat: add 4844 header fields and consensus checks (#3972)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rjected authored Jul 29, 2023
1 parent 15bb1c9 commit 334d606
Show file tree
Hide file tree
Showing 12 changed files with 368 additions and 7 deletions.
2 changes: 2 additions & 0 deletions crates/consensus/auto-seal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ impl StorageInner {
nonce: 0,
base_fee_per_gas,
extra_data: Default::default(),
blob_gas_used: None,
excess_blob_gas: None,
};

header.transactions_root = if transactions.is_empty() {
Expand Down
91 changes: 89 additions & 2 deletions crates/consensus/common/src/validation.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
//! Collection of methods for block validation.
use reth_interfaces::{consensus::ConsensusError, Result as RethResult};
use reth_primitives::{
constants, BlockNumber, ChainSpec, Hardfork, Header, InvalidTransactionError, SealedBlock,
SealedHeader, Transaction, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy,
blobfee::calculate_excess_blob_gas,
constants::{
self,
eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK},
},
BlockNumber, ChainSpec, Hardfork, Header, InvalidTransactionError, SealedBlock, SealedHeader,
Transaction, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy,
};
use reth_provider::{AccountReader, HeaderProvider, WithdrawalsProvider};
use std::collections::{hash_map::Entry, HashMap};
Expand Down Expand Up @@ -38,6 +43,15 @@ pub fn validate_header_standalone(
return Err(ConsensusError::WithdrawalsRootUnexpected)
}

// Ensures that EIP-4844 fields are valid once cancun is active.
if chain_spec.fork(Hardfork::Cancun).active_at_timestamp(header.timestamp) {
validate_4844_header_standalone(header)?;
} else if header.blob_gas_used.is_some() {
return Err(ConsensusError::BlobGasUsedUnexpected)
} else if header.excess_blob_gas.is_some() {
return Err(ConsensusError::ExcessBlobGasUnexpected)
}

Ok(())
}

Expand Down Expand Up @@ -291,6 +305,11 @@ pub fn validate_header_regarding_parent(
}
}

// ensure that the blob gas fields for this block
if chain_spec.fork(Hardfork::Cancun).active_at_timestamp(child.timestamp) {
validate_4844_header_with_parent(parent, child)?;
}

Ok(())
}

Expand Down Expand Up @@ -376,6 +395,72 @@ pub fn full_validation<Provider: HeaderProvider + AccountReader + WithdrawalsPro
Ok(())
}

/// Validates that the EIP-4844 header fields are correct with respect to the parent block. This
/// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and
/// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the
/// parent header fields.
pub fn validate_4844_header_with_parent(
parent: &SealedHeader,
child: &SealedHeader,
) -> 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, calculate_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);

if child.blob_gas_used.is_none() {
return Err(ConsensusError::BlobGasUsedMissing)
}
let excess_blob_gas = child.excess_blob_gas.ok_or(ConsensusError::ExcessBlobGasMissing)?;

let expected_excess_blob_gas =
calculate_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used);
if expected_excess_blob_gas != excess_blob_gas {
return Err(ConsensusError::ExcessBlobGasDiff {
expected: expected_excess_blob_gas,
got: excess_blob_gas,
parent_excess_blob_gas,
parent_blob_gas_used,
})
}

Ok(())
}

/// Validates that the EIP-4844 header fields exist and conform to the spec. This ensures that:
///
/// * `blob_gas_used` exists as a header field
/// * `excess_blob_gas` exists as a header field
/// * `blob_gas_used` is less than or equal to `MAX_DATA_GAS_PER_BLOCK`
/// * `blob_gas_used` is a multiple of `DATA_GAS_PER_BLOB`
pub fn validate_4844_header_standalone(header: &SealedHeader) -> Result<(), ConsensusError> {
let blob_gas_used = header.blob_gas_used.ok_or(ConsensusError::BlobGasUsedMissing)?;

if header.excess_blob_gas.is_none() {
return Err(ConsensusError::ExcessBlobGasMissing)
}

if blob_gas_used > MAX_DATA_GAS_PER_BLOCK {
return Err(ConsensusError::BlobGasUsedExceedsMaxBlobGasPerBlock {
blob_gas_used,
max_blob_gas_per_block: MAX_DATA_GAS_PER_BLOCK,
})
}

if blob_gas_used % DATA_GAS_PER_BLOB != 0 {
return Err(ConsensusError::BlobGasUsedNotMultipleOfBlobGasPerBlob {
blob_gas_used,
blob_gas_per_blob: DATA_GAS_PER_BLOB,
})
}

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -530,6 +615,8 @@ mod tests {
nonce: 0x0000000000000000,
base_fee_per_gas: 0x28f0001df.into(),
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
};
// size: 0x9b5

Expand Down
21 changes: 21 additions & 0 deletions crates/interfaces/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ pub enum ConsensusError {
WithdrawalIndexInvalid { got: u64, expected: u64 },
#[error("Missing withdrawals")]
BodyWithdrawalsMissing,
#[error("Missing blob gas used")]
BlobGasUsedMissing,
#[error("Unexpected blob gas used")]
BlobGasUsedUnexpected,
#[error("Missing excess blob gas")]
ExcessBlobGasMissing,
#[error("Unexpected excess blob gas")]
ExcessBlobGasUnexpected,
#[error("Blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
BlobGasUsedExceedsMaxBlobGasPerBlock { blob_gas_used: u64, max_blob_gas_per_block: u64 },
#[error(
"Blob gas used {blob_gas_used} is not a multiple of blob gas per blob {blob_gas_per_blob}"
)]
BlobGasUsedNotMultipleOfBlobGasPerBlob { blob_gas_used: u64, blob_gas_per_blob: u64 },
#[error("Invalid excess blob gas. Expected: {expected}, got: {got}. Parent excess blob gas: {parent_excess_blob_gas}, parent blob gas used: {parent_blob_gas_used}.")]
ExcessBlobGasDiff {
expected: u64,
got: u64,
parent_excess_blob_gas: u64,
parent_blob_gas_used: u64,
},
/// Error for a transaction that violates consensus.
#[error(transparent)]
InvalidTransaction(#[from] InvalidTransactionError),
Expand Down
8 changes: 8 additions & 0 deletions crates/net/eth-wire/src/types/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ mod test {
nonce: 0x0000000000000000u64,
base_fee_per_gas: None,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
},
]),
}.encode(&mut data);
Expand Down Expand Up @@ -289,6 +291,8 @@ mod test {
nonce: 0x0000000000000000u64,
base_fee_per_gas: None,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
},
]),
};
Expand Down Expand Up @@ -401,6 +405,8 @@ mod test {
nonce: 0x0000000000000000u64,
base_fee_per_gas: None,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
},
],
withdrawals: None,
Expand Down Expand Up @@ -485,6 +491,8 @@ mod test {
nonce: 0x0000000000000000u64,
base_fee_per_gas: None,
withdrawals_root: None,
blob_gas_used: None,
excess_blob_gas: None,
},
],
withdrawals: None,
Expand Down
4 changes: 4 additions & 0 deletions crates/payload/basic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,8 @@ fn build_payload<Pool, Client>(
difficulty: U256::ZERO,
gas_used: cumulative_gas_used,
extra_data: extra_data.into(),
blob_gas_used: None,
excess_blob_gas: None,
};

// seal the block
Expand Down Expand Up @@ -785,6 +787,8 @@ where
difficulty: U256::ZERO,
gas_used: 0,
extra_data: extra_data.into(),
blob_gas_used: None,
excess_blob_gas: None,
};

let block = Block { header, body: vec![], ommers: vec![], withdrawals };
Expand Down
12 changes: 12 additions & 0 deletions crates/primitives/src/blobfee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! Helpers for working with EIP-4844 blob fee

use crate::constants::eip4844::TARGET_DATA_GAS_PER_BLOCK;

/// Calculates the excess data gas for the next block, after applying the current set of blobs on
/// top of the excess data gas.
///
/// Specified in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension)
pub fn calculate_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 {
let excess_blob_gas = parent_excess_blob_gas + parent_blob_gas_used;
excess_blob_gas.saturating_sub(TARGET_DATA_GAS_PER_BLOCK)
}
Loading

0 comments on commit 334d606

Please sign in to comment.