feat: eip-7702 (#9214)

Co-authored-by: Matthew Smith <m@lattejed.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com>
Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
Oliver
2024-07-11 07:45:47 +02:00
committed by GitHub
parent fc3d0eb9d7
commit fc4c037e60
25 changed files with 877 additions and 72 deletions

View File

@ -241,7 +241,8 @@ impl InvalidPoolTransactionError {
}
InvalidTransactionError::Eip2930Disabled |
InvalidTransactionError::Eip1559Disabled |
InvalidTransactionError::Eip4844Disabled => {
InvalidTransactionError::Eip4844Disabled |
InvalidTransactionError::Eip7702Disabled => {
// settings
false
}

View File

@ -749,6 +749,10 @@ impl EthPoolTransaction for MockTransaction {
_ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
}
}
fn authorization_count(&self) -> usize {
0
}
}
impl TryFromRecoveredTransaction for MockTransaction {

View File

@ -14,7 +14,7 @@ use reth_primitives::{
BlobTransactionSidecar, BlobTransactionValidationError, FromRecoveredPooledTransaction,
IntoRecoveredTransaction, PooledTransactionsElement, PooledTransactionsElementEcRecovered,
SealedBlock, Transaction, TransactionSignedEcRecovered, TryFromRecoveredTransaction, TxHash,
TxKind, B256, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID, U256,
TxKind, B256, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, U256,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -842,6 +842,11 @@ pub trait PoolTransaction:
self.tx_type() == EIP4844_TX_TYPE_ID
}
/// Returns true if the transaction is an EIP-7702 transaction.
fn is_eip7702(&self) -> bool {
self.tx_type() == EIP7702_TX_TYPE_ID
}
/// Returns the length of the rlp encoded transaction object
///
/// Note: Implementations should cache this value.
@ -866,6 +871,9 @@ pub trait EthPoolTransaction: PoolTransaction {
blob: &BlobTransactionSidecar,
settings: &KzgSettings,
) -> Result<(), BlobTransactionValidationError>;
/// Returns the number of authorizations this transaction has.
fn authorization_count(&self) -> usize;
}
/// The default [`PoolTransaction`] for the [Pool](crate::Pool) for Ethereum.
@ -938,6 +946,9 @@ impl EthPooledTransaction {
blob_sidecar = EthBlobTransactionSidecar::Missing;
U256::from(t.max_fee_per_gas).saturating_mul(U256::from(t.gas_limit))
}
Transaction::Eip7702(t) => {
U256::from(t.max_fee_per_gas).saturating_mul(U256::from(t.gas_limit))
}
_ => U256::ZERO,
};
let mut cost = transaction.value();
@ -1024,6 +1035,7 @@ impl PoolTransaction for EthPooledTransaction {
Transaction::Eip2930(tx) => tx.gas_price,
Transaction::Eip1559(tx) => tx.max_fee_per_gas,
Transaction::Eip4844(tx) => tx.max_fee_per_gas,
Transaction::Eip7702(tx) => tx.max_fee_per_gas,
_ => 0,
}
}
@ -1041,6 +1053,7 @@ impl PoolTransaction for EthPooledTransaction {
Transaction::Legacy(_) | Transaction::Eip2930(_) => None,
Transaction::Eip1559(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::Eip4844(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::Eip7702(tx) => Some(tx.max_priority_fee_per_gas),
_ => None,
}
}
@ -1120,6 +1133,13 @@ impl EthPoolTransaction for EthPooledTransaction {
_ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
}
}
fn authorization_count(&self) -> usize {
match &self.transaction.transaction {
Transaction::Eip7702(tx) => tx.authorization_list.len(),
_ => 0,
}
}
}
impl TryFromRecoveredTransaction for EthPooledTransaction {
@ -1130,7 +1150,7 @@ impl TryFromRecoveredTransaction for EthPooledTransaction {
) -> Result<Self, Self::Error> {
// ensure we can handle the transaction type and its format
match tx.tx_type() as u8 {
0..=EIP1559_TX_TYPE_ID => {
0..=EIP1559_TX_TYPE_ID | EIP7702_TX_TYPE_ID => {
// supported
}
EIP4844_TX_TYPE_ID => {

View File

@ -12,14 +12,14 @@ use crate::{
use reth_chainspec::{ChainSpec, EthereumHardforks};
use reth_primitives::{
constants::{eip4844::MAX_BLOBS_PER_BLOCK, ETHEREUM_BLOCK_GAS_LIMIT},
GotExpected, InvalidTransactionError, SealedBlock, TxKind, EIP1559_TX_TYPE_ID,
EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
GotExpected, InvalidTransactionError, SealedBlock, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
};
use reth_provider::{AccountReader, BlockReaderIdExt, StateProviderFactory};
use reth_tasks::TaskSpawner;
use revm::{
interpreter::gas::validate_initial_tx_gas,
primitives::{AccessListItem, EnvKzgSettings, SpecId},
primitives::{EnvKzgSettings, SpecId},
};
use std::{
marker::PhantomData,
@ -119,6 +119,8 @@ pub(crate) struct EthTransactionValidatorInner<Client, T> {
eip1559: bool,
/// Fork indicator whether we are using EIP-4844 blob transactions.
eip4844: bool,
/// Fork indicator whether we are using EIP-7702 type transactions.
eip7702: bool,
/// The current max gas limit
block_gas_limit: u64,
/// Minimum priority fee to enforce for acceptance into the pool.
@ -185,6 +187,15 @@ where
)
}
}
EIP7702_TX_TYPE_ID => {
// Reject EIP-7702 transactions.
if !self.eip7702 {
return TransactionValidationOutcome::Invalid(
transaction,
InvalidTransactionError::Eip7702Disabled.into(),
)
}
}
_ => {
return TransactionValidationOutcome::Invalid(
@ -255,9 +266,17 @@ where
}
}
// intrinsic gas checks
let is_shanghai = self.fork_tracker.is_shanghai_activated();
if let Err(err) = ensure_intrinsic_gas(&transaction, is_shanghai) {
if transaction.is_eip7702() {
// Cancun fork is required for 7702 txs
if !self.fork_tracker.is_prague_activated() {
return TransactionValidationOutcome::Invalid(
transaction,
InvalidTransactionError::TxTypeNotSupported.into(),
)
}
}
if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
return TransactionValidationOutcome::Invalid(transaction, err)
}
@ -407,6 +426,10 @@ where
if self.chain_spec.is_shanghai_active_at_timestamp(new_tip_block.timestamp) {
self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
}
if self.chain_spec.is_prague_active_at_timestamp(new_tip_block.timestamp) {
self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
}
@ -418,12 +441,16 @@ pub struct EthTransactionValidatorBuilder {
shanghai: bool,
/// Fork indicator whether we are in the Cancun hardfork.
cancun: bool,
/// Fork indicator whether we are in the Cancun hardfork.
prague: bool,
/// Whether using EIP-2718 type transactions is allowed
eip2718: bool,
/// Whether using EIP-1559 type transactions is allowed
eip1559: bool,
/// Whether using EIP-4844 type transactions is allowed
eip4844: bool,
/// Whether using EIP-7702 type transactions is allowed
eip7702: bool,
/// The current max gas limit
block_gas_limit: u64,
/// Minimum priority fee to enforce for acceptance into the pool.
@ -464,12 +491,16 @@ impl EthTransactionValidatorBuilder {
eip2718: true,
eip1559: true,
eip4844: true,
eip7702: true,
// shanghai is activated by default
shanghai: true,
// cancun is activated by default
cancun: true,
// prague not yet activated
prague: false,
}
}
@ -504,6 +535,17 @@ impl EthTransactionValidatorBuilder {
self
}
/// Disables the Prague fork.
pub const fn no_prague(self) -> Self {
self.set_prague(false)
}
/// Set the Prague fork.
pub const fn set_prague(mut self, prague: bool) -> Self {
self.prague = prague;
self
}
/// Disables the support for EIP-2718 transactions.
pub const fn no_eip2718(self) -> Self {
self.set_eip2718(false)
@ -591,9 +633,11 @@ impl EthTransactionValidatorBuilder {
chain_spec,
shanghai,
cancun,
prague,
eip2718,
eip1559,
eip4844,
eip7702,
block_gas_limit,
minimum_priority_fee,
kzg_settings,
@ -602,8 +646,11 @@ impl EthTransactionValidatorBuilder {
..
} = self;
let fork_tracker =
ForkTracker { shanghai: AtomicBool::new(shanghai), cancun: AtomicBool::new(cancun) };
let fork_tracker = ForkTracker {
shanghai: AtomicBool::new(shanghai),
cancun: AtomicBool::new(cancun),
prague: AtomicBool::new(prague),
};
let inner = EthTransactionValidatorInner {
chain_spec,
@ -612,6 +659,7 @@ impl EthTransactionValidatorBuilder {
eip1559,
fork_tracker,
eip4844,
eip7702,
block_gas_limit,
minimum_priority_fee,
blob_store: Box::new(blob_store),
@ -670,23 +718,30 @@ impl EthTransactionValidatorBuilder {
/// Keeps track of whether certain forks are activated
#[derive(Debug)]
pub(crate) struct ForkTracker {
pub struct ForkTracker {
/// Tracks if shanghai is activated at the block's timestamp.
pub(crate) shanghai: AtomicBool,
pub shanghai: AtomicBool,
/// Tracks if cancun is activated at the block's timestamp.
pub(crate) cancun: AtomicBool,
pub cancun: AtomicBool,
/// Tracks if prague is activated at the block's timestamp.
pub prague: AtomicBool,
}
impl ForkTracker {
/// Returns `true` if Shanghai fork is activated.
pub(crate) fn is_shanghai_activated(&self) -> bool {
pub fn is_shanghai_activated(&self) -> bool {
self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
}
/// Returns `true` if Cancun fork is activated.
pub(crate) fn is_cancun_activated(&self) -> bool {
pub fn is_cancun_activated(&self) -> bool {
self.cancun.load(std::sync::atomic::Ordering::Relaxed)
}
/// Returns `true` if Prague fork is activated.
pub fn is_prague_activated(&self) -> bool {
self.prague.load(std::sync::atomic::Ordering::Relaxed)
}
}
/// Ensure that the code size is not greater than `max_init_code_size`.
@ -707,39 +762,34 @@ pub fn ensure_max_init_code_size<T: PoolTransaction>(
/// Ensures that gas limit of the transaction exceeds the intrinsic gas of the transaction.
///
/// See also [`calculate_intrinsic_gas_after_merge`]
pub fn ensure_intrinsic_gas<T: PoolTransaction>(
/// Caution: This only checks past the Merge hardfork.
pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
transaction: &T,
is_shanghai: bool,
fork_tracker: &ForkTracker,
) -> Result<(), InvalidPoolTransactionError> {
if transaction.gas_limit() <
calculate_intrinsic_gas_after_merge(
transaction.input(),
&transaction.kind(),
transaction.access_list().map(|list| list.0.as_slice()).unwrap_or(&[]),
is_shanghai,
)
{
let spec_id = if fork_tracker.is_prague_activated() {
SpecId::PRAGUE
} else if fork_tracker.is_shanghai_activated() {
SpecId::SHANGHAI
} else {
SpecId::MERGE
};
let gas_after_merge = validate_initial_tx_gas(
spec_id,
transaction.input(),
transaction.kind().is_create(),
transaction.access_list().map(|list| list.0.as_slice()).unwrap_or(&[]),
transaction.authorization_count() as u64,
);
if transaction.gas_limit() < gas_after_merge {
Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
} else {
Ok(())
}
}
/// Calculates the Intrinsic Gas usage for a Transaction
///
/// Caution: This only checks past the Merge hardfork.
#[inline]
pub fn calculate_intrinsic_gas_after_merge(
input: &[u8],
kind: &TxKind,
access_list: &[AccessListItem],
is_shanghai: bool,
) -> u64 {
let spec_id = if is_shanghai { SpecId::SHANGHAI } else { SpecId::MERGE };
validate_initial_tx_gas(spec_id, input, kind.is_create(), access_list, 0)
}
#[cfg(test)]
mod tests {
use super::*;
@ -764,10 +814,14 @@ mod tests {
#[tokio::test]
async fn validate_transaction() {
let transaction = get_transaction();
let mut fork_tracker =
ForkTracker { shanghai: false.into(), cancun: false.into(), prague: false.into() };
let res = ensure_intrinsic_gas(&transaction, false);
let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
assert!(res.is_ok());
let res = ensure_intrinsic_gas(&transaction, true);
fork_tracker.shanghai = true.into();
let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
assert!(res.is_ok());
let provider = MockEthProvider::default();