From afebb2b20b584a8664feb4042309679d47b1131e Mon Sep 17 00:00:00 2001 From: "Supernovahs.eth" <91280922+supernovahs@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:03:40 +0530 Subject: [PATCH] intrinsic gas check (#4867) Co-authored-by: Matthias Seitz --- Cargo.lock | 3 +++ crates/primitives/src/lib.rs | 2 ++ .../primitives/src/transaction/access_list.rs | 26 ++++++++++++++----- crates/primitives/src/transaction/mod.rs | 24 +++++++++++++++++ crates/revm/revm-primitives/Cargo.toml | 1 + crates/revm/revm-primitives/src/compat.rs | 23 +++++++++++++++- crates/rpc/rpc/src/eth/error.rs | 3 +++ crates/rpc/rpc/src/eth/revm_utils.rs | 2 +- crates/transaction-pool/Cargo.toml | 3 ++- crates/transaction-pool/src/error.rs | 5 ++++ .../transaction-pool/src/test_utils/mock.rs | 10 ++++++- crates/transaction-pool/src/traits.rs | 17 +++++++++++- crates/transaction-pool/src/validate/eth.rs | 20 ++++++++++++++ 13 files changed, 128 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5a0f8acec..4b5ed5ac2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6145,6 +6145,7 @@ name = "reth-revm-primitives" version = "0.1.0-alpha.10" dependencies = [ "reth-primitives", + "revm", ] [[package]] @@ -6402,7 +6403,9 @@ dependencies = [ "reth-metrics", "reth-primitives", "reth-provider", + "reth-revm-primitives", "reth-tasks", + "revm", "serde", "thiserror", "tokio", diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 693aacc433..1aeaf9c5ef 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -41,6 +41,8 @@ mod receipt; pub mod serde_helper; pub mod stage; mod storage; + +/// Helpers for working with transactions mod transaction; pub mod trie; mod withdrawal; diff --git a/crates/primitives/src/transaction/access_list.rs b/crates/primitives/src/transaction/access_list.rs index 6a5abd7d79..ce3bcad197 100644 --- a/crates/primitives/src/transaction/access_list.rs +++ b/crates/primitives/src/transaction/access_list.rs @@ -1,10 +1,9 @@ -use std::mem; - use crate::{Address, B256}; +use alloy_primitives::U256; use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use reth_codecs::{main_codec, Compact}; -use revm_primitives::U256; use serde::{Deserialize, Serialize}; +use std::mem; /// A list of addresses and storage keys that the transaction plans to access. /// Accesses outside the list are possible, but become more expensive. @@ -47,12 +46,17 @@ pub struct AccessList( impl AccessList { /// Converts the list into a vec, expected by revm - pub fn flattened(self) -> Vec<(Address, Vec)> { + pub fn flattened(&self) -> Vec<(Address, Vec)> { self.flatten().collect() } - /// Returns an iterator over the list's addresses and storage keys. - pub fn flatten(self) -> impl Iterator)> { + /// Consumes the type and converts the list into a vec, expected by revm + pub fn into_flattened(self) -> Vec<(Address, Vec)> { + self.into_flatten().collect() + } + + /// Consumes the type and returns an iterator over the list's addresses and storage keys. + pub fn into_flatten(self) -> impl Iterator)> { self.0.into_iter().map(|item| { ( item.address, @@ -61,6 +65,16 @@ impl AccessList { }) } + /// Returns an iterator over the list's addresses and storage keys. + pub fn flatten(&self) -> impl Iterator)> + '_ { + self.0.iter().map(|item| { + ( + item.address, + item.storage_keys.iter().map(|slot| U256::from_be_bytes(slot.0)).collect(), + ) + }) + } + /// Calculates a heuristic for the in-memory size of the [AccessList]. #[inline] pub fn size(&self) -> usize { diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 740d445105..d0562dcf30 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -178,6 +178,18 @@ impl Transaction { } } + /// Returns the [AccessList] of the transaction. + /// + /// Returns `None` for legacy transactions. + pub fn access_list(&self) -> Option<&AccessList> { + match self { + Transaction::Legacy(_) => None, + Transaction::Eip2930(tx) => Some(&tx.access_list), + Transaction::Eip1559(tx) => Some(&tx.access_list), + Transaction::Eip4844(tx) => Some(&tx.access_list), + } + } + /// Get the gas limit of the transaction. pub fn gas_limit(&self) -> u64 { match self { @@ -565,6 +577,18 @@ impl TransactionKind { } } + /// Returns true if the transaction is a contract creation. + #[inline] + pub fn is_create(self) -> bool { + matches!(self, TransactionKind::Create) + } + + /// Returns true if the transaction is a contract call. + #[inline] + pub fn is_call(self) -> bool { + matches!(self, TransactionKind::Call(_)) + } + /// Calculates a heuristic for the in-memory size of the [TransactionKind]. #[inline] fn size(self) -> usize { diff --git a/crates/revm/revm-primitives/Cargo.toml b/crates/revm/revm-primitives/Cargo.toml index 41f706f238..ff3bc9f1f5 100644 --- a/crates/revm/revm-primitives/Cargo.toml +++ b/crates/revm/revm-primitives/Cargo.toml @@ -11,3 +11,4 @@ description = "core reth specific revm utilities" [dependencies] # reth reth-primitives.workspace = true +revm.workspace = true \ No newline at end of file diff --git a/crates/revm/revm-primitives/src/compat.rs b/crates/revm/revm-primitives/src/compat.rs index 8b6f0b22d3..07de35ae31 100644 --- a/crates/revm/revm-primitives/src/compat.rs +++ b/crates/revm/revm-primitives/src/compat.rs @@ -1,6 +1,10 @@ use reth_primitives::{ revm_primitives::{AccountInfo, Log}, - Account, Log as RethLog, KECCAK_EMPTY, + Account, Address, Log as RethLog, TransactionKind, KECCAK_EMPTY, U256, +}; +use revm::{ + interpreter::gas::initial_tx_gas, + primitives::{MergeSpec, ShanghaiSpec}, }; /// Check equality between Revm and Reth `Log`s. @@ -38,3 +42,20 @@ pub fn into_revm_acc(reth_acc: Account) -> AccountInfo { code: None, } } + +/// 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: &TransactionKind, + access_list: &[(Address, Vec)], + is_shanghai: bool, +) -> u64 { + if is_shanghai { + initial_tx_gas::(input, kind.is_create(), access_list) + } else { + initial_tx_gas::(input, kind.is_create(), access_list) + } +} diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index e5ae757482..d8eb1e0eb8 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -576,6 +576,9 @@ impl From for RpcPoolError { InvalidPoolTransactionError::ExceedsMaxInitCodeSize(_, _) => { RpcPoolError::ExceedsMaxInitCodeSize } + InvalidPoolTransactionError::IntrinsicGasTooLow => { + RpcPoolError::Invalid(RpcInvalidTransactionError::GasTooLow) + } InvalidPoolTransactionError::OversizedData(_, _) => RpcPoolError::OversizedData, InvalidPoolTransactionError::Underpriced => RpcPoolError::Underpriced, InvalidPoolTransactionError::Other(err) => RpcPoolError::PoolTransactionError(err), diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 62aedc227c..0281515b53 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -309,7 +309,7 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR value: value.unwrap_or_default(), data: input.try_into_unique_input()?.unwrap_or_default(), chain_id: chain_id.map(|c| c.to()), - access_list: access_list.map(AccessList::flattened).unwrap_or_default(), + access_list: access_list.map(AccessList::into_flattened).unwrap_or_default(), // EIP-4844 fields blob_hashes: blob_versioned_hashes.unwrap_or_default(), max_fee_per_blob_gas, diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index b99a02bf9c..f6f5ab97b1 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -22,7 +22,8 @@ reth-primitives.workspace = true reth-provider.workspace = true reth-interfaces.workspace = true reth-tasks.workspace = true - +revm.workspace = true +reth-revm-primitives = { path = "../revm/revm-primitives" } alloy-rlp.workspace = true # async/futures diff --git a/crates/transaction-pool/src/error.rs b/crates/transaction-pool/src/error.rs index f405f96301..01ced749e2 100644 --- a/crates/transaction-pool/src/error.rs +++ b/crates/transaction-pool/src/error.rs @@ -187,6 +187,10 @@ pub enum InvalidPoolTransactionError { /// Any other error that occurred while inserting/validating that is transaction specific #[error("{0:?}")] Other(Box), + /// The transaction is specified to use less gas than required to start the + /// invocation. + #[error("intrinsic gas too low")] + IntrinsicGasTooLow, } // === impl InvalidPoolTransactionError === @@ -240,6 +244,7 @@ impl InvalidPoolTransactionError { // local setting false } + InvalidPoolTransactionError::IntrinsicGasTooLow => true, InvalidPoolTransactionError::Overdraft => false, InvalidPoolTransactionError::Other(err) => err.is_bad_transaction(), InvalidPoolTransactionError::Eip4844(eip4844_err) => { diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index 666035f0f4..bb069d5ec7 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -13,7 +13,7 @@ use rand::{ }; use reth_primitives::{ constants::{eip4844::DATA_GAS_PER_BLOB, MIN_PROTOCOL_BASE_FEE}, - hex, Address, FromRecoveredPooledTransaction, FromRecoveredTransaction, + hex, AccessList, Address, FromRecoveredPooledTransaction, FromRecoveredTransaction, IntoRecoveredTransaction, PooledTransactionsElementEcRecovered, Signature, Transaction, TransactionKind, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxHash, TxLegacy, TxType, TxValue, B256, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID, @@ -420,6 +420,10 @@ impl PoolTransaction for MockTransaction { } } + fn access_list(&self) -> Option<&AccessList> { + None + } + fn max_priority_fee_per_gas(&self) -> Option { match self { MockTransaction::Legacy { .. } => None, @@ -470,6 +474,10 @@ impl PoolTransaction for MockTransaction { } } + fn input(&self) -> &[u8] { + &[] + } + fn size(&self) -> usize { 0 } diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index 5aaec28f7e..d05e8b3d2c 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -7,7 +7,7 @@ use crate::{ use alloy_rlp::Encodable; use futures_util::{ready, Stream}; use reth_primitives::{ - Address, BlobTransactionSidecar, BlobTransactionValidationError, + AccessList, Address, BlobTransactionSidecar, BlobTransactionValidationError, FromRecoveredPooledTransaction, FromRecoveredTransaction, IntoRecoveredTransaction, PeerId, PooledTransactionsElement, PooledTransactionsElementEcRecovered, SealedBlock, Transaction, TransactionKind, TransactionSignedEcRecovered, TxEip4844, TxHash, B256, EIP1559_TX_TYPE_ID, @@ -696,6 +696,10 @@ pub trait PoolTransaction: /// This is also commonly referred to as the "Gas Fee Cap" (`GasFeeCap`). fn max_fee_per_gas(&self) -> u128; + /// Returns the access_list for the particular transaction type. + /// For Legacy transactions, returns default. + fn access_list(&self) -> Option<&AccessList>; + /// Returns the EIP-1559 Priority fee the caller is paying to the block author. /// /// This will return `None` for non-EIP1559 transactions @@ -720,6 +724,9 @@ pub trait PoolTransaction: /// [`TransactionKind::Create`] if the transaction is a contract creation. fn kind(&self) -> &TransactionKind; + /// Returns the input data of this transaction. + fn input(&self) -> &[u8]; + /// Returns a measurement of the heap usage of this type and all its internals. fn size(&self) -> usize; @@ -910,6 +917,10 @@ impl PoolTransaction for EthPooledTransaction { self.transaction.max_fee_per_blob_gas() } + fn access_list(&self) -> Option<&AccessList> { + self.transaction.access_list() + } + /// Returns the effective tip for this transaction. /// /// For EIP-1559 transactions: `min(max_fee_per_gas - base_fee, max_priority_fee_per_gas)`. @@ -930,6 +941,10 @@ impl PoolTransaction for EthPooledTransaction { self.transaction.kind() } + fn input(&self) -> &[u8] { + self.transaction.input().as_ref() + } + /// Returns a measurement of the heap usage of this type and all its internals. fn size(&self) -> usize { self.transaction.transaction.input().len() diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 936793ea80..1b94a34e9f 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -18,6 +18,7 @@ use reth_primitives::{ EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, }; use reth_provider::{AccountReader, StateProviderFactory}; +use reth_revm_primitives::calculate_intrinsic_gas_after_merge; use reth_tasks::TaskSpawner; use std::{ marker::PhantomData, @@ -200,6 +201,25 @@ where } } + // intrinsic gas checks + let access_list = + transaction.access_list().map(|list| list.flattened()).unwrap_or_default(); + let is_shanghai = self.fork_tracker.is_shanghai_activated(); + + if transaction.gas_limit() < + calculate_intrinsic_gas_after_merge( + transaction.input(), + transaction.kind(), + &access_list, + is_shanghai, + ) + { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::IntrinsicGasTooLow, + ) + } + let mut maybe_blob_sidecar = None; // blob tx checks