intrinsic gas check (#4867)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Supernovahs.eth
2023-10-04 18:03:40 +05:30
committed by GitHub
parent 081d71e1a2
commit afebb2b20b
13 changed files with 128 additions and 11 deletions

3
Cargo.lock generated
View File

@ -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",

View File

@ -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;

View File

@ -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<U256>)> {
pub fn flattened(&self) -> Vec<(Address, Vec<U256>)> {
self.flatten().collect()
}
/// Returns an iterator over the list's addresses and storage keys.
pub fn flatten(self) -> impl Iterator<Item = (Address, Vec<U256>)> {
/// Consumes the type and converts the list into a vec, expected by revm
pub fn into_flattened(self) -> Vec<(Address, Vec<U256>)> {
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<Item = (Address, Vec<U256>)> {
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<Item = (Address, Vec<U256>)> + '_ {
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 {

View File

@ -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 {

View File

@ -11,3 +11,4 @@ description = "core reth specific revm utilities"
[dependencies]
# reth
reth-primitives.workspace = true
revm.workspace = true

View File

@ -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<U256>)],
is_shanghai: bool,
) -> u64 {
if is_shanghai {
initial_tx_gas::<ShanghaiSpec>(input, kind.is_create(), access_list)
} else {
initial_tx_gas::<MergeSpec>(input, kind.is_create(), access_list)
}
}

View File

@ -576,6 +576,9 @@ impl From<InvalidPoolTransactionError> 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),

View File

@ -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,

View File

@ -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

View File

@ -187,6 +187,10 @@ pub enum InvalidPoolTransactionError {
/// Any other error that occurred while inserting/validating that is transaction specific
#[error("{0:?}")]
Other(Box<dyn PoolTransactionError>),
/// 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) => {

View File

@ -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<u128> {
match self {
MockTransaction::Legacy { .. } => None,
@ -470,6 +474,10 @@ impl PoolTransaction for MockTransaction {
}
}
fn input(&self) -> &[u8] {
&[]
}
fn size(&self) -> usize {
0
}

View File

@ -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()

View File

@ -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