mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
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:
@ -241,7 +241,8 @@ impl InvalidPoolTransactionError {
|
||||
}
|
||||
InvalidTransactionError::Eip2930Disabled |
|
||||
InvalidTransactionError::Eip1559Disabled |
|
||||
InvalidTransactionError::Eip4844Disabled => {
|
||||
InvalidTransactionError::Eip4844Disabled |
|
||||
InvalidTransactionError::Eip7702Disabled => {
|
||||
// settings
|
||||
false
|
||||
}
|
||||
|
||||
@ -749,6 +749,10 @@ impl EthPoolTransaction for MockTransaction {
|
||||
_ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
|
||||
}
|
||||
}
|
||||
|
||||
fn authorization_count(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromRecoveredTransaction for MockTransaction {
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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();
|
||||
|
||||
Reference in New Issue
Block a user