diff --git a/crates/edr_eth/src/eips/eip2718.rs b/crates/edr_eth/src/eips/eip2718.rs index 2418eb72c..bb85aa338 100644 --- a/crates/edr_eth/src/eips/eip2718.rs +++ b/crates/edr_eth/src/eips/eip2718.rs @@ -120,8 +120,8 @@ impl, LogT> Receipt for TypedEnvelope { self.data().logs_bloom() } - fn logs(&self) -> &[LogT] { - self.data().logs() + fn transaction_logs(&self) -> &[LogT] { + self.data().transaction_logs() } fn root_or_status(&self) -> receipt::RootOrStatus<'_> { diff --git a/crates/edr_eth/src/receipt.rs b/crates/edr_eth/src/receipt.rs index 29f9c0a5d..fb7609569 100644 --- a/crates/edr_eth/src/receipt.rs +++ b/crates/edr_eth/src/receipt.rs @@ -66,7 +66,7 @@ pub trait Receipt { /// Returns the bloom filter of the logs generated within this transaction. fn logs_bloom(&self) -> &Bloom; /// Returns the logs generated within this transaction. - fn logs(&self) -> &[LogT]; + fn transaction_logs(&self) -> &[LogT]; /// Returns the state root (pre-EIP-658) or status (post-EIP-658) of the /// receipt. fn root_or_status(&self) -> RootOrStatus<'_>; diff --git a/crates/edr_eth/src/receipt/execution.rs b/crates/edr_eth/src/receipt/execution.rs index dddf73ac8..19722d5a0 100644 --- a/crates/edr_eth/src/receipt/execution.rs +++ b/crates/edr_eth/src/receipt/execution.rs @@ -173,7 +173,7 @@ impl Receipt for Execution { } } - fn logs(&self) -> &[LogT] { + fn transaction_logs(&self) -> &[LogT] { match self { Execution::Legacy(receipt) => &receipt.logs, Execution::Eip658(receipt) => &receipt.logs, diff --git a/crates/edr_eth/src/receipt/transaction.rs b/crates/edr_eth/src/receipt/transaction.rs index ca90f8851..b60b46bc5 100644 --- a/crates/edr_eth/src/receipt/transaction.rs +++ b/crates/edr_eth/src/receipt/transaction.rs @@ -5,7 +5,7 @@ use revm_primitives::{EvmWiring, ExecutionResult, Output}; use super::{MapReceiptLogs, Receipt}; use crate::{ - transaction::{SignedTransaction, TransactionType}, + transaction::{ExecutableTransaction, Transaction, TransactionType}, Address, Bloom, SpecId, B256, U256, }; @@ -52,7 +52,7 @@ impl, LogT> TransactionReceipt( execution_receipt: ExecutionReceiptT, - transaction: &impl SignedTransaction, + transaction: &(impl Transaction + ExecutableTransaction), result: &ExecutionResult, transaction_index: u64, block_base_fee: U256, @@ -131,8 +131,8 @@ impl, LogT> Receipt self.inner.logs_bloom() } - fn logs(&self) -> &[LogT] { - self.inner.logs() + fn transaction_logs(&self) -> &[LogT] { + self.inner.transaction_logs() } fn root_or_status(&self) -> super::RootOrStatus<'_> { diff --git a/crates/edr_eth/src/signature.rs b/crates/edr_eth/src/signature.rs index 9d8d7036f..375e76a56 100644 --- a/crates/edr_eth/src/signature.rs +++ b/crates/edr_eth/src/signature.rs @@ -8,7 +8,8 @@ mod fakeable; mod recovery_id; mod y_parity; -use k256::{elliptic_curve::sec1::ToEncodedPoint, FieldBytes, PublicKey, SecretKey}; +pub use k256::SecretKey; +use k256::{elliptic_curve::sec1::ToEncodedPoint, FieldBytes, PublicKey}; use sha3::{Digest, Keccak256}; pub use self::{recovery_id::SignatureWithRecoveryId, y_parity::SignatureWithYParity}; diff --git a/crates/edr_eth/src/transaction.rs b/crates/edr_eth/src/transaction.rs index 77a86daac..a02c5117b 100644 --- a/crates/edr_eth/src/transaction.rs +++ b/crates/edr_eth/src/transaction.rs @@ -17,7 +17,7 @@ use std::str::FromStr; pub use revm_primitives::{alloy_primitives::TxKind, Transaction, TransactionValidation}; use revm_primitives::{ruint, B256}; -use crate::{AccessListItem, Address, Bytes, U256, U8}; +use crate::{signature::Signature, Bytes, U256, U8}; pub const INVALID_TX_TYPE_ERROR_MESSAGE: &str = "invalid tx type"; @@ -72,6 +72,18 @@ impl From for u8 { } } +impl IsEip4844 for Type { + fn is_eip4844(&self) -> bool { + matches!(self, Type::Eip4844) + } +} + +impl IsLegacy for Type { + fn is_legacy(&self) -> bool { + matches!(self, Type::Legacy) + } +} + #[derive(Debug, thiserror::Error)] pub enum ParseError { #[error("{0}")] @@ -148,14 +160,15 @@ impl serde::Serialize for Type { } } -pub trait SignedTransaction: Transaction + TransactionType { +/// Trait for information about executable transactions. +pub trait ExecutableTransaction { /// The effective gas price of the transaction, calculated using the /// provided block base fee. Only applicable for post-EIP-1559 transactions. fn effective_gas_price(&self, block_base_fee: U256) -> Option; /// The maximum fee per gas the sender is willing to pay. Only applicable /// for post-EIP-1559 transactions. - fn max_fee_per_gas(&self) -> Option; + fn max_fee_per_gas(&self) -> Option<&U256>; /// The enveloped (EIP-2718) RLP-encoding of the transaction. fn rlp_encoding(&self) -> &Bytes; @@ -168,71 +181,67 @@ pub trait SignedTransaction: Transaction + TransactionType { fn transaction_hash(&self) -> &B256; } +/// Trait for transactions that may be signed. +pub trait MaybeSignedTransaction { + /// Returns the [`Signature`] of the transaction, if any. + fn maybe_signature(&self) -> Option<&dyn Signature>; +} + +/// Trait for transactions that have been signed. +pub trait SignedTransaction { + /// Returns the [`Signature`] of the transaction. + fn signature(&self) -> &dyn Signature; +} + +impl MaybeSignedTransaction for TransactionT { + fn maybe_signature(&self) -> Option<&dyn Signature> { + Some(self.signature()) + } +} + +/// Trait for mutable transactions. pub trait TransactionMut { /// Sets the gas limit of the transaction. fn set_gas_limit(&mut self, gas_limit: u64); } +/// Trait for determining the type of a transaction. pub trait TransactionType { /// Type of the transaction. - type Type; + type Type: Into; /// Returns the type of the transaction. fn transaction_type(&self) -> Self::Type; } -pub fn max_cost(transaction: &impl SignedTransaction) -> U256 { - U256::from(transaction.gas_limit()).saturating_mul(*transaction.gas_price()) +/// Trait for determining whether a transaction has an access list. +pub trait HasAccessList { + /// Whether the transaction has an access list. + fn has_access_list(&self) -> bool; } -pub fn upfront_cost(transaction: &impl SignedTransaction) -> U256 { - max_cost(transaction).saturating_add(*transaction.value()) +/// Trait for determining whether a transaction is an EIP-155 transaction. +pub trait IsEip155 { + /// Whether the transaction is an EIP-155 transaction. + fn is_eip155(&self) -> bool; } -/// Represents _all_ transaction requests received from RPC -#[derive(Clone, Debug, PartialEq, Eq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct EthTransactionRequest { - /// from address - pub from: Address, - /// to address - #[cfg_attr(feature = "serde", serde(default))] - pub to: Option
, - /// legacy, gas Price - #[cfg_attr(feature = "serde", serde(default))] - pub gas_price: Option, - /// max base fee per gas sender is willing to pay - #[cfg_attr(feature = "serde", serde(default))] - pub max_fee_per_gas: Option, - /// miner tip - #[cfg_attr(feature = "serde", serde(default))] - pub max_priority_fee_per_gas: Option, - /// gas - #[cfg_attr(feature = "serde", serde(default, with = "crate::serde::optional_u64"))] - pub gas: Option, - /// value of th tx in wei - pub value: Option, - /// Any additional data sent - #[cfg_attr(feature = "serde", serde(alias = "input"))] - pub data: Option, - /// Transaction nonce - #[cfg_attr(feature = "serde", serde(default, with = "crate::serde::optional_u64"))] - pub nonce: Option, - /// Chain ID - #[cfg_attr(feature = "serde", serde(default, with = "crate::serde::optional_u64"))] - pub chain_id: Option, - /// warm storage access pre-payment - #[cfg_attr(feature = "serde", serde(default))] - pub access_list: Option>, - /// EIP-2718 type - #[cfg_attr( - feature = "serde", - serde(default, rename = "type", with = "crate::serde::optional_u8") - )] - pub transaction_type: Option, - /// Blobs (EIP-4844) - pub blobs: Option>, - /// Blob versioned hashes (EIP-4844) - pub blob_hashes: Option>, +/// Trait for determining whether a transaction is an EIP-4844 transaction. +pub trait IsEip4844 { + /// Whether the transaction is an EIP-4844 transaction. + fn is_eip4844(&self) -> bool; +} + +/// Trait for determining whether a transaction is a legacy transaction. +pub trait IsLegacy { + /// Whether the transaction is a legacy transaction. + fn is_legacy(&self) -> bool; +} + +pub fn max_cost(transaction: &impl Transaction) -> U256 { + U256::from(transaction.gas_limit()).saturating_mul(*transaction.gas_price()) +} + +pub fn upfront_cost(transaction: &impl Transaction) -> U256 { + max_cost(transaction).saturating_add(*transaction.value()) } diff --git a/crates/edr_eth/src/transaction/fake_signature.rs b/crates/edr_eth/src/transaction/fake_signature.rs index 2be822cd5..86d7ac910 100644 --- a/crates/edr_eth/src/transaction/fake_signature.rs +++ b/crates/edr_eth/src/transaction/fake_signature.rs @@ -4,6 +4,8 @@ pub(super) mod tests { () => { #[test] fn hash_with_fake_signature_same_sender() { + use $crate::transaction::ExecutableTransaction as _; + let transaction_request = dummy_request(); let sender = Address::from(revm_primitives::ruint::aliases::U160::from(1)); @@ -19,6 +21,8 @@ pub(super) mod tests { #[test] fn hash_with_fake_signature_different_senders() { + use $crate::transaction::ExecutableTransaction as _; + let transaction_request = dummy_request(); let sender_one = Address::from(revm_primitives::ruint::aliases::U160::from(1)); @@ -35,6 +39,8 @@ pub(super) mod tests { #[test] fn recovers_fake_sender() { + use $crate::transaction::Transaction as _; + let transaction_request = dummy_request(); // Fails to recover with signature error if tried to ecrocver a fake signature diff --git a/crates/edr_eth/src/transaction/pooled.rs b/crates/edr_eth/src/transaction/pooled.rs index 1d394e6b0..70952aa41 100644 --- a/crates/edr_eth/src/transaction/pooled.rs +++ b/crates/edr_eth/src/transaction/pooled.rs @@ -1,10 +1,13 @@ mod eip4844; pub use self::eip4844::Eip4844; -use super::Signed; use crate::{ - transaction::{signed::PreOrPostEip155, INVALID_TX_TYPE_ERROR_MESSAGE}, + transaction::{ + signed::PreOrPostEip155, ExecutableTransaction, IsEip155, Signed, Transaction, TxKind, + INVALID_TX_TYPE_ERROR_MESSAGE, + }, utils::enveloped, + AccessListItem, Address, Bytes, B256, U256, }; pub type Legacy = super::signed::Legacy; @@ -120,6 +123,58 @@ impl alloy_rlp::Encodable for PooledTransaction { } } +impl ExecutableTransaction for PooledTransaction { + fn effective_gas_price(&self, block_base_fee: U256) -> Option { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.effective_gas_price(block_base_fee), + PooledTransaction::PostEip155Legacy(tx) => tx.effective_gas_price(block_base_fee), + PooledTransaction::Eip2930(tx) => tx.effective_gas_price(block_base_fee), + PooledTransaction::Eip1559(tx) => tx.effective_gas_price(block_base_fee), + PooledTransaction::Eip4844(tx) => tx.effective_gas_price(block_base_fee), + } + } + + fn max_fee_per_gas(&self) -> Option<&U256> { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.max_fee_per_gas(), + PooledTransaction::PostEip155Legacy(tx) => tx.max_fee_per_gas(), + PooledTransaction::Eip2930(tx) => tx.max_fee_per_gas(), + PooledTransaction::Eip1559(tx) => tx.max_fee_per_gas(), + PooledTransaction::Eip4844(tx) => tx.max_fee_per_gas(), + } + } + + fn rlp_encoding(&self) -> &Bytes { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.rlp_encoding(), + PooledTransaction::PostEip155Legacy(tx) => tx.rlp_encoding(), + PooledTransaction::Eip2930(tx) => tx.rlp_encoding(), + PooledTransaction::Eip1559(tx) => tx.rlp_encoding(), + PooledTransaction::Eip4844(tx) => tx.rlp_encoding(), + } + } + + fn total_blob_gas(&self) -> Option { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.total_blob_gas(), + PooledTransaction::PostEip155Legacy(tx) => tx.total_blob_gas(), + PooledTransaction::Eip2930(tx) => tx.total_blob_gas(), + PooledTransaction::Eip1559(tx) => tx.total_blob_gas(), + PooledTransaction::Eip4844(tx) => tx.total_blob_gas(), + } + } + + fn transaction_hash(&self) -> &B256 { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.transaction_hash(), + PooledTransaction::PostEip155Legacy(tx) => tx.transaction_hash(), + PooledTransaction::Eip2930(tx) => tx.transaction_hash(), + PooledTransaction::Eip1559(tx) => tx.transaction_hash(), + PooledTransaction::Eip4844(tx) => tx.transaction_hash(), + } + } +} + impl From for PooledTransaction { fn from(value: Legacy) -> Self { PooledTransaction::PreEip155Legacy(value) @@ -159,6 +214,150 @@ impl From for PooledTransaction { } } +impl From for Signed { + fn from(value: PooledTransaction) -> Self { + value.into_payload() + } +} + +impl IsEip155 for PooledTransaction { + fn is_eip155(&self) -> bool { + matches!(self, PooledTransaction::PostEip155Legacy(_)) + } +} + +impl Transaction for PooledTransaction { + fn caller(&self) -> &Address { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.caller(), + PooledTransaction::PostEip155Legacy(tx) => tx.caller(), + PooledTransaction::Eip2930(tx) => tx.caller(), + PooledTransaction::Eip1559(tx) => tx.caller(), + PooledTransaction::Eip4844(tx) => tx.caller(), + } + } + + fn gas_limit(&self) -> u64 { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.gas_limit(), + PooledTransaction::PostEip155Legacy(tx) => tx.gas_limit(), + PooledTransaction::Eip2930(tx) => tx.gas_limit(), + PooledTransaction::Eip1559(tx) => tx.gas_limit(), + PooledTransaction::Eip4844(tx) => tx.gas_limit(), + } + } + + fn gas_price(&self) -> &U256 { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.gas_price(), + PooledTransaction::PostEip155Legacy(tx) => tx.gas_price(), + PooledTransaction::Eip2930(tx) => tx.gas_price(), + PooledTransaction::Eip1559(tx) => tx.gas_price(), + PooledTransaction::Eip4844(tx) => tx.gas_price(), + } + } + + fn kind(&self) -> TxKind { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.kind(), + PooledTransaction::PostEip155Legacy(tx) => tx.kind(), + PooledTransaction::Eip2930(tx) => tx.kind(), + PooledTransaction::Eip1559(tx) => tx.kind(), + PooledTransaction::Eip4844(tx) => tx.kind(), + } + } + + fn value(&self) -> &U256 { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.value(), + PooledTransaction::PostEip155Legacy(tx) => tx.value(), + PooledTransaction::Eip2930(tx) => tx.value(), + PooledTransaction::Eip1559(tx) => tx.value(), + PooledTransaction::Eip4844(tx) => tx.value(), + } + } + + fn data(&self) -> &Bytes { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.data(), + PooledTransaction::PostEip155Legacy(tx) => tx.data(), + PooledTransaction::Eip2930(tx) => tx.data(), + PooledTransaction::Eip1559(tx) => tx.data(), + PooledTransaction::Eip4844(tx) => tx.data(), + } + } + + fn nonce(&self) -> u64 { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.nonce(), + PooledTransaction::PostEip155Legacy(tx) => tx.nonce(), + PooledTransaction::Eip2930(tx) => tx.nonce(), + PooledTransaction::Eip1559(tx) => tx.nonce(), + PooledTransaction::Eip4844(tx) => tx.nonce(), + } + } + + fn chain_id(&self) -> Option { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.chain_id(), + PooledTransaction::PostEip155Legacy(tx) => tx.chain_id(), + PooledTransaction::Eip2930(tx) => tx.chain_id(), + PooledTransaction::Eip1559(tx) => tx.chain_id(), + PooledTransaction::Eip4844(tx) => tx.chain_id(), + } + } + + fn access_list(&self) -> &[AccessListItem] { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.access_list(), + PooledTransaction::PostEip155Legacy(tx) => tx.access_list(), + PooledTransaction::Eip2930(tx) => tx.access_list(), + PooledTransaction::Eip1559(tx) => tx.access_list(), + PooledTransaction::Eip4844(tx) => tx.access_list(), + } + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.max_priority_fee_per_gas(), + PooledTransaction::PostEip155Legacy(tx) => tx.max_priority_fee_per_gas(), + PooledTransaction::Eip2930(tx) => tx.max_priority_fee_per_gas(), + PooledTransaction::Eip1559(tx) => tx.max_priority_fee_per_gas(), + PooledTransaction::Eip4844(tx) => tx.max_priority_fee_per_gas(), + } + } + + fn blob_hashes(&self) -> &[B256] { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.blob_hashes(), + PooledTransaction::PostEip155Legacy(tx) => tx.blob_hashes(), + PooledTransaction::Eip2930(tx) => tx.blob_hashes(), + PooledTransaction::Eip1559(tx) => tx.blob_hashes(), + PooledTransaction::Eip4844(tx) => tx.blob_hashes(), + } + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.max_fee_per_blob_gas(), + PooledTransaction::PostEip155Legacy(tx) => tx.max_fee_per_blob_gas(), + PooledTransaction::Eip2930(tx) => tx.max_fee_per_blob_gas(), + PooledTransaction::Eip1559(tx) => tx.max_fee_per_blob_gas(), + PooledTransaction::Eip4844(tx) => tx.max_fee_per_blob_gas(), + } + } + + fn authorization_list(&self) -> Option<&revm_primitives::AuthorizationList> { + match self { + PooledTransaction::PreEip155Legacy(tx) => tx.authorization_list(), + PooledTransaction::PostEip155Legacy(tx) => tx.authorization_list(), + PooledTransaction::Eip2930(tx) => tx.authorization_list(), + PooledTransaction::Eip1559(tx) => tx.authorization_list(), + PooledTransaction::Eip4844(tx) => tx.authorization_list(), + } + } +} + #[cfg(test)] mod tests { use std::{str::FromStr, sync::OnceLock}; diff --git a/crates/edr_eth/src/transaction/pooled/eip4844.rs b/crates/edr_eth/src/transaction/pooled/eip4844.rs index 809e1bb7b..924e3dc82 100644 --- a/crates/edr_eth/src/transaction/pooled/eip4844.rs +++ b/crates/edr_eth/src/transaction/pooled/eip4844.rs @@ -1,7 +1,14 @@ -use revm_primitives::{EnvKzgSettings, B256, VERSIONED_HASH_VERSION_KZG}; +use std::sync::OnceLock; + +use alloy_rlp::Encodable as _; +use revm_primitives::{AuthorizationList, EnvKzgSettings, VERSIONED_HASH_VERSION_KZG}; use sha2::Digest; -use crate::{transaction, Blob, Bytes48}; +use crate::{ + transaction::{self, ExecutableTransaction, Transaction, TxKind}, + utils::enveloped, + AccessListItem, Address, Blob, Bytes, Bytes48, B256, U256, +}; /// An EIP-4844 pooled transaction. #[derive(Clone, Debug, PartialEq, Eq)] @@ -10,6 +17,7 @@ pub struct Eip4844 { blobs: Vec, commitments: Vec, proofs: Vec, + rlp_encoding: OnceLock, } #[derive(Debug, thiserror::Error)] @@ -107,6 +115,7 @@ impl Eip4844 { blobs, commitments, proofs, + rlp_encoding: OnceLock::new(), }) } @@ -115,6 +124,11 @@ impl Eip4844 { &self.blobs } + /// Returns a reference to the blobs of the pooled transaction. + pub fn blobs_ref(&self) -> &Vec { + &self.blobs + } + /// Returns the commitments of the pooled transaction. pub fn commitments(&self) -> &[Bytes48] { &self.commitments @@ -173,6 +187,86 @@ impl Eip4844 { } } +impl ExecutableTransaction for Eip4844 { + fn effective_gas_price(&self, block_base_fee: U256) -> Option { + self.payload.effective_gas_price(block_base_fee) + } + + fn max_fee_per_gas(&self) -> Option<&U256> { + self.payload.max_fee_per_gas() + } + + fn rlp_encoding(&self) -> &Bytes { + self.rlp_encoding.get_or_init(|| { + let mut encoded = Vec::with_capacity(1 + self.length()); + enveloped(Self::TYPE, self, &mut encoded); + encoded.into() + }) + } + + fn total_blob_gas(&self) -> Option { + self.payload.total_blob_gas() + } + + fn transaction_hash(&self) -> &B256 { + self.payload.transaction_hash() + } +} + +impl Transaction for Eip4844 { + fn caller(&self) -> &Address { + self.payload.caller() + } + + fn gas_limit(&self) -> u64 { + self.payload.gas_limit() + } + + fn gas_price(&self) -> &U256 { + self.payload.gas_price() + } + + fn kind(&self) -> TxKind { + self.payload.kind() + } + + fn value(&self) -> &U256 { + self.payload.value() + } + + fn data(&self) -> &Bytes { + self.payload.data() + } + + fn nonce(&self) -> u64 { + self.payload.nonce() + } + + fn chain_id(&self) -> Option { + self.payload.chain_id() + } + + fn access_list(&self) -> &[AccessListItem] { + self.payload.access_list() + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + self.payload.max_priority_fee_per_gas() + } + + fn blob_hashes(&self) -> &[B256] { + self.payload.blob_hashes() + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + self.payload.max_fee_per_blob_gas() + } + + fn authorization_list(&self) -> Option<&AuthorizationList> { + self.payload.authorization_list() + } +} + #[repr(transparent)] struct RlpBlob<'blob>(&'blob Blob); diff --git a/crates/edr_eth/src/transaction/request.rs b/crates/edr_eth/src/transaction/request.rs index 3cfd62371..c0a8a151c 100644 --- a/crates/edr_eth/src/transaction/request.rs +++ b/crates/edr_eth/src/transaction/request.rs @@ -9,7 +9,10 @@ use k256::SecretKey; pub use self::{ eip155::Eip155, eip1559::Eip1559, eip2930::Eip2930, eip4844::Eip4844, legacy::Legacy, }; -use super::{Request, Signed}; +use super::{ + signed::{FakeSign, Sign}, + Request, Signed, +}; use crate::{signature::SignatureError, Address, U256}; impl Request { @@ -73,14 +76,26 @@ impl Request { Request::Eip4844(transaction) => transaction.sign(secret_key)?.into(), }) } +} + +impl FakeSign for Request { + type Signed = Signed; - /// Signs the transaction with the provided secret key, belonging to the - /// provided sender's address. - /// - /// # Safety - /// - /// The `caller` and `secret_key` must correspond to the same account. - pub unsafe fn sign_for_sender_unchecked( + fn fake_sign(self, sender: Address) -> Signed { + match self { + Request::Legacy(transaction) => transaction.fake_sign(sender).into(), + Request::Eip155(transaction) => transaction.fake_sign(sender).into(), + Request::Eip2930(transaction) => transaction.fake_sign(sender).into(), + Request::Eip1559(transaction) => transaction.fake_sign(sender).into(), + Request::Eip4844(transaction) => transaction.fake_sign(sender).into(), + } + } +} + +impl Sign for Request { + type Signed = Signed; + + unsafe fn sign_for_sender_unchecked( self, secret_key: &SecretKey, caller: Address, @@ -103,23 +118,13 @@ impl Request { .into(), }) } - - pub fn fake_sign(self, sender: Address) -> Signed { - match self { - Request::Legacy(transaction) => transaction.fake_sign(sender).into(), - Request::Eip155(transaction) => transaction.fake_sign(sender).into(), - Request::Eip2930(transaction) => transaction.fake_sign(sender).into(), - Request::Eip1559(transaction) => transaction.fake_sign(sender).into(), - Request::Eip4844(transaction) => transaction.fake_sign(sender).into(), - } - } } /// A transaction request and the sender's address. #[derive(Clone, Debug)] -pub struct TransactionRequestAndSender { +pub struct TransactionRequestAndSender { /// The transaction request. - pub request: Request, + pub request: RequestT, /// The sender's address. pub sender: Address, } diff --git a/crates/edr_eth/src/transaction/request/eip155.rs b/crates/edr_eth/src/transaction/request/eip155.rs index bc6bb091f..a6e48d7d0 100644 --- a/crates/edr_eth/src/transaction/request/eip155.rs +++ b/crates/edr_eth/src/transaction/request/eip155.rs @@ -137,6 +137,8 @@ impl Encodable for Eip155 { mod tests { use std::str::FromStr; + use transaction::ExecutableTransaction as _; + use super::*; use crate::transaction::fake_signature::tests::test_fake_sign_properties; diff --git a/crates/edr_eth/src/transaction/request/eip1559.rs b/crates/edr_eth/src/transaction/request/eip1559.rs index 2f03a642f..6967cdae9 100644 --- a/crates/edr_eth/src/transaction/request/eip1559.rs +++ b/crates/edr_eth/src/transaction/request/eip1559.rs @@ -100,6 +100,8 @@ impl Eip1559 { pub(crate) mod tests { use std::str::FromStr; + use transaction::ExecutableTransaction as _; + use super::*; use crate::transaction::fake_signature::tests::test_fake_sign_properties; diff --git a/crates/edr_eth/src/transaction/request/eip2930.rs b/crates/edr_eth/src/transaction/request/eip2930.rs index 3b06e7843..d82933181 100644 --- a/crates/edr_eth/src/transaction/request/eip2930.rs +++ b/crates/edr_eth/src/transaction/request/eip2930.rs @@ -97,6 +97,8 @@ impl Eip2930 { mod tests { use std::str::FromStr; + use transaction::ExecutableTransaction as _; + use super::*; use crate::transaction::fake_signature::tests::test_fake_sign_properties; diff --git a/crates/edr_eth/src/transaction/request/legacy.rs b/crates/edr_eth/src/transaction/request/legacy.rs index 086029252..c333f21ae 100644 --- a/crates/edr_eth/src/transaction/request/legacy.rs +++ b/crates/edr_eth/src/transaction/request/legacy.rs @@ -88,6 +88,8 @@ impl Legacy { mod tests { use std::str::FromStr; + use transaction::ExecutableTransaction as _; + use super::*; use crate::transaction::fake_signature::tests::test_fake_sign_properties; diff --git a/crates/edr_eth/src/transaction/signed.rs b/crates/edr_eth/src/transaction/signed.rs index a089b5cd5..c47e25cec 100644 --- a/crates/edr_eth/src/transaction/signed.rs +++ b/crates/edr_eth/src/transaction/signed.rs @@ -7,6 +7,7 @@ mod legacy; use std::sync::OnceLock; use alloy_rlp::{Buf, BufMut}; +use k256::SecretKey; pub use self::{ eip155::Eip155, @@ -16,25 +17,46 @@ pub use self::{ legacy::{Legacy, PreOrPostEip155}, }; use super::{ - Signed, SignedTransaction, TransactionMut, TransactionType, TxKind, - INVALID_TX_TYPE_ERROR_MESSAGE, + ExecutableTransaction, HasAccessList, IsEip155, IsEip4844, IsLegacy, Signed, SignedTransaction, + Transaction, TransactionMut, TransactionType, TxKind, INVALID_TX_TYPE_ERROR_MESSAGE, }; use crate::{ - signature::{Fakeable, Signature}, + signature::{Fakeable, Signature, SignatureError}, AccessListItem, Address, Bytes, B256, U256, }; +/// Trait for signing a transaction request with a fake signature. +pub trait FakeSign { + /// The type of the signed transaction. + type Signed; + + /// Signs the transaction with a fake signature. + fn fake_sign(self, sender: Address) -> Self::Signed; +} + +pub trait Sign { + /// The type of the signed transaction. + type Signed; + + /// Signs the transaction with the provided secret key, belonging to the + /// provided sender's address. + /// + /// # Safety + /// + /// The `caller` and `secret_key` must correspond to the same account. + unsafe fn sign_for_sender_unchecked( + self, + secret_key: &SecretKey, + caller: Address, + ) -> Result; +} + impl Signed { /// Whether this is a legacy (pre-EIP-155) transaction. - pub fn is_legacy(&self) -> bool { + pub fn is_pre_eip155(&self) -> bool { matches!(self, Signed::PreEip155Legacy(_)) } - /// Whether this is an EIP-1559 transaction. - pub fn is_eip155(&self) -> bool { - matches!(self, Signed::PostEip155Legacy(_)) - } - /// Whether this is an EIP-1559 transaction. pub fn is_eip1559(&self) -> bool { matches!(self, Signed::Eip1559(_)) @@ -45,11 +67,6 @@ impl Signed { matches!(self, Signed::Eip2930(_)) } - /// Whether this is an EIP-4844 transaction. - pub fn is_eip4844(&self) -> bool { - matches!(self, Signed::Eip4844(_)) - } - pub fn as_legacy(&self) -> Option<&self::legacy::Legacy> { match self { Signed::PreEip155Legacy(tx) => Some(tx), @@ -57,17 +74,6 @@ impl Signed { } } - /// Returns the [`Signature`] of the transaction - pub fn signature(&self) -> &dyn Signature { - match self { - Signed::PreEip155Legacy(tx) => &tx.signature, - Signed::PostEip155Legacy(tx) => &tx.signature, - Signed::Eip2930(tx) => &tx.signature, - Signed::Eip1559(tx) => &tx.signature, - Signed::Eip4844(tx) => &tx.signature, - } - } - pub fn is_invalid_transaction_type_error(message: &str) -> bool { message == INVALID_TX_TYPE_ERROR_MESSAGE } @@ -179,26 +185,54 @@ impl From for Signed { } } -impl SignedTransaction for Signed { +impl HasAccessList for Signed { + fn has_access_list(&self) -> bool { + match self { + Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) => false, + Signed::Eip2930(_) | Signed::Eip1559(_) | Signed::Eip4844(_) => true, + } + } +} + +impl IsEip155 for Signed { + fn is_eip155(&self) -> bool { + matches!(self, Signed::PostEip155Legacy(_)) + } +} + +impl IsEip4844 for Signed { + fn is_eip4844(&self) -> bool { + matches!(self, Signed::Eip4844(_)) + } +} + +impl IsLegacy for Signed { + fn is_legacy(&self) -> bool { + matches!( + self, + Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) + ) + } +} + +impl ExecutableTransaction for Signed { fn effective_gas_price(&self, block_base_fee: U256) -> Option { match self { - Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) | Signed::Eip2930(_) => None, - Signed::Eip1559(tx) => Some( - tx.max_fee_per_gas - .min(block_base_fee + tx.max_priority_fee_per_gas), - ), - Signed::Eip4844(tx) => Some( - tx.max_fee_per_gas - .min(block_base_fee + tx.max_priority_fee_per_gas), - ), + Signed::PreEip155Legacy(tx) => tx.effective_gas_price(block_base_fee), + Signed::PostEip155Legacy(tx) => tx.effective_gas_price(block_base_fee), + Signed::Eip2930(tx) => tx.effective_gas_price(block_base_fee), + Signed::Eip1559(tx) => tx.effective_gas_price(block_base_fee), + Signed::Eip4844(tx) => tx.effective_gas_price(block_base_fee), } } - fn max_fee_per_gas(&self) -> Option { + fn max_fee_per_gas(&self) -> Option<&U256> { match self { - Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) | Signed::Eip2930(_) => None, - Signed::Eip1559(tx) => Some(tx.max_fee_per_gas), - Signed::Eip4844(tx) => Some(tx.max_fee_per_gas), + Signed::PreEip155Legacy(tx) => tx.max_fee_per_gas(), + Signed::PostEip155Legacy(tx) => tx.max_fee_per_gas(), + Signed::Eip2930(tx) => tx.max_fee_per_gas(), + Signed::Eip1559(tx) => tx.max_fee_per_gas(), + Signed::Eip4844(tx) => tx.max_fee_per_gas(), } } @@ -214,8 +248,11 @@ impl SignedTransaction for Signed { fn total_blob_gas(&self) -> Option { match self { - Signed::Eip4844(tx) => Some(tx.total_blob_gas()), - _ => None, + Signed::PreEip155Legacy(tx) => tx.total_blob_gas(), + Signed::PostEip155Legacy(tx) => tx.total_blob_gas(), + Signed::Eip2930(tx) => tx.total_blob_gas(), + Signed::Eip1559(tx) => tx.total_blob_gas(), + Signed::Eip4844(tx) => tx.total_blob_gas(), } } @@ -230,7 +267,19 @@ impl SignedTransaction for Signed { } } -impl revm_primitives::Transaction for Signed { +impl SignedTransaction for Signed { + fn signature(&self) -> &dyn Signature { + match self { + Signed::PreEip155Legacy(tx) => &tx.signature, + Signed::PostEip155Legacy(tx) => &tx.signature, + Signed::Eip2930(tx) => &tx.signature, + Signed::Eip1559(tx) => &tx.signature, + Signed::Eip4844(tx) => &tx.signature, + } + } +} + +impl Transaction for Signed { fn caller(&self) -> &Address { match self { Signed::PreEip155Legacy(tx) => tx.caller(), @@ -243,113 +292,122 @@ impl revm_primitives::Transaction for Signed { fn gas_limit(&self) -> u64 { match self { - Signed::PreEip155Legacy(tx) => tx.gas_limit, - Signed::PostEip155Legacy(tx) => tx.gas_limit, - Signed::Eip2930(tx) => tx.gas_limit, - Signed::Eip1559(tx) => tx.gas_limit, - Signed::Eip4844(tx) => tx.gas_limit, + Signed::PreEip155Legacy(tx) => tx.gas_limit(), + Signed::PostEip155Legacy(tx) => tx.gas_limit(), + Signed::Eip2930(tx) => tx.gas_limit(), + Signed::Eip1559(tx) => tx.gas_limit(), + Signed::Eip4844(tx) => tx.gas_limit(), } } fn gas_price(&self) -> &U256 { match self { - Signed::PreEip155Legacy(tx) => &tx.gas_price, - Signed::PostEip155Legacy(tx) => &tx.gas_price, - Signed::Eip2930(tx) => &tx.gas_price, - Signed::Eip1559(tx) => &tx.max_fee_per_gas, - Signed::Eip4844(tx) => &tx.max_fee_per_gas, + Signed::PreEip155Legacy(tx) => tx.gas_price(), + Signed::PostEip155Legacy(tx) => tx.gas_price(), + Signed::Eip2930(tx) => tx.gas_price(), + Signed::Eip1559(tx) => tx.gas_price(), + Signed::Eip4844(tx) => tx.gas_price(), } } fn kind(&self) -> TxKind { match self { - Signed::PreEip155Legacy(tx) => tx.kind, - Signed::PostEip155Legacy(tx) => tx.kind, - Signed::Eip2930(tx) => tx.kind, - Signed::Eip1559(tx) => tx.kind, - Signed::Eip4844(tx) => TxKind::Call(tx.to), + Signed::PreEip155Legacy(tx) => tx.kind(), + Signed::PostEip155Legacy(tx) => tx.kind(), + Signed::Eip2930(tx) => tx.kind(), + Signed::Eip1559(tx) => tx.kind(), + Signed::Eip4844(tx) => tx.kind(), } } fn value(&self) -> &U256 { match self { - Signed::PreEip155Legacy(tx) => &tx.value, - Signed::PostEip155Legacy(tx) => &tx.value, - Signed::Eip2930(tx) => &tx.value, - Signed::Eip1559(tx) => &tx.value, - Signed::Eip4844(tx) => &tx.value, + Signed::PreEip155Legacy(tx) => tx.value(), + Signed::PostEip155Legacy(tx) => tx.value(), + Signed::Eip2930(tx) => tx.value(), + Signed::Eip1559(tx) => tx.value(), + Signed::Eip4844(tx) => tx.value(), } } fn data(&self) -> &Bytes { match self { - Signed::PreEip155Legacy(tx) => &tx.input, - Signed::PostEip155Legacy(tx) => &tx.input, - Signed::Eip2930(tx) => &tx.input, - Signed::Eip1559(tx) => &tx.input, - Signed::Eip4844(tx) => &tx.input, + Signed::PreEip155Legacy(tx) => tx.data(), + Signed::PostEip155Legacy(tx) => tx.data(), + Signed::Eip2930(tx) => tx.data(), + Signed::Eip1559(tx) => tx.data(), + Signed::Eip4844(tx) => tx.data(), } } fn nonce(&self) -> u64 { match self { - Signed::PreEip155Legacy(t) => t.nonce, - Signed::PostEip155Legacy(t) => t.nonce, - Signed::Eip2930(t) => t.nonce, - Signed::Eip1559(t) => t.nonce, - Signed::Eip4844(t) => t.nonce, + Signed::PreEip155Legacy(tx) => tx.nonce(), + Signed::PostEip155Legacy(tx) => tx.nonce(), + Signed::Eip2930(tx) => tx.nonce(), + Signed::Eip1559(tx) => tx.nonce(), + Signed::Eip4844(tx) => tx.nonce(), } } fn chain_id(&self) -> Option { match self { - Signed::PreEip155Legacy(_) => None, - Signed::PostEip155Legacy(t) => Some(t.chain_id()), - Signed::Eip2930(t) => Some(t.chain_id), - Signed::Eip1559(t) => Some(t.chain_id), - Signed::Eip4844(t) => Some(t.chain_id), + Signed::PreEip155Legacy(tx) => tx.chain_id(), + Signed::PostEip155Legacy(tx) => tx.chain_id(), + Signed::Eip2930(tx) => tx.chain_id(), + Signed::Eip1559(tx) => tx.chain_id(), + Signed::Eip4844(tx) => tx.chain_id(), } } fn access_list(&self) -> &[AccessListItem] { match self { - Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) => &[], - Signed::Eip2930(tx) => &tx.access_list.0, - Signed::Eip1559(tx) => &tx.access_list.0, - Signed::Eip4844(tx) => &tx.access_list.0, + Signed::PreEip155Legacy(tx) => tx.access_list(), + Signed::PostEip155Legacy(tx) => tx.access_list(), + Signed::Eip2930(tx) => tx.access_list(), + Signed::Eip1559(tx) => tx.access_list(), + Signed::Eip4844(tx) => tx.access_list(), } } fn max_priority_fee_per_gas(&self) -> Option<&U256> { match self { - Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) | Signed::Eip2930(_) => None, - Signed::Eip1559(tx) => Some(&tx.max_priority_fee_per_gas), - Signed::Eip4844(tx) => Some(&tx.max_priority_fee_per_gas), + Signed::PreEip155Legacy(tx) => tx.max_priority_fee_per_gas(), + Signed::PostEip155Legacy(tx) => tx.max_priority_fee_per_gas(), + Signed::Eip2930(tx) => tx.max_priority_fee_per_gas(), + Signed::Eip1559(tx) => tx.max_priority_fee_per_gas(), + Signed::Eip4844(tx) => tx.max_priority_fee_per_gas(), } } fn blob_hashes(&self) -> &[B256] { match self { - Signed::PreEip155Legacy(_) - | Signed::PostEip155Legacy(_) - | Signed::Eip2930(_) - | Signed::Eip1559(_) => &[], - Signed::Eip4844(tx) => &tx.blob_hashes, + Signed::PreEip155Legacy(tx) => tx.blob_hashes(), + Signed::PostEip155Legacy(tx) => tx.blob_hashes(), + Signed::Eip2930(tx) => tx.blob_hashes(), + Signed::Eip1559(tx) => tx.blob_hashes(), + Signed::Eip4844(tx) => tx.blob_hashes(), } } fn max_fee_per_blob_gas(&self) -> Option<&U256> { match self { - Signed::PreEip155Legacy(_) - | Signed::PostEip155Legacy(_) - | Signed::Eip2930(_) - | Signed::Eip1559(_) => None, - Signed::Eip4844(tx) => Some(&tx.max_fee_per_blob_gas), + Signed::PreEip155Legacy(tx) => tx.max_fee_per_blob_gas(), + Signed::PostEip155Legacy(tx) => tx.max_fee_per_blob_gas(), + Signed::Eip2930(tx) => tx.max_fee_per_blob_gas(), + Signed::Eip1559(tx) => tx.max_fee_per_blob_gas(), + Signed::Eip4844(tx) => tx.max_fee_per_blob_gas(), } } fn authorization_list(&self) -> Option<&revm_primitives::AuthorizationList> { - None + match self { + Signed::PreEip155Legacy(tx) => tx.authorization_list(), + Signed::PostEip155Legacy(tx) => tx.authorization_list(), + Signed::Eip2930(tx) => tx.authorization_list(), + Signed::Eip1559(tx) => tx.authorization_list(), + Signed::Eip4844(tx) => tx.authorization_list(), + } } } @@ -389,11 +447,7 @@ mod tests { use alloy_rlp::Decodable as _; use super::*; - use crate::{ - signature, - transaction::{self, Transaction as _}, - AccessList, Bytes, - }; + use crate::{signature, transaction, AccessList, Bytes}; #[test] fn can_recover_sender() { diff --git a/crates/edr_eth/src/transaction/signed/eip155.rs b/crates/edr_eth/src/transaction/signed/eip155.rs index 2edb4afaa..85d2756f4 100644 --- a/crates/edr_eth/src/transaction/signed/eip155.rs +++ b/crates/edr_eth/src/transaction/signed/eip155.rs @@ -1,12 +1,12 @@ use std::sync::OnceLock; use alloy_rlp::RlpEncodable; -use revm_primitives::keccak256; +use revm_primitives::{keccak256, AuthorizationList}; use crate::{ signature::{self, Signature}, - transaction::{self, TxKind}, - Address, Bytes, B256, U256, + transaction::{self, ExecutableTransaction, Transaction, TxKind}, + AccessListItem, Address, Bytes, B256, U256, }; #[derive(Clone, Debug, Eq, RlpEncodable)] @@ -37,24 +37,27 @@ pub struct Eip155 { impl Eip155 { /// The type identifier for a post-EIP-155 transaction. pub const TYPE: u8 = transaction::request::Eip155::TYPE; +} - /// Returns the caller/signer of the transaction. - pub fn caller(&self) -> &Address { - self.signature.caller() +impl ExecutableTransaction for Eip155 { + fn effective_gas_price(&self, _block_base_fee: U256) -> Option { + None } - pub fn chain_id(&self) -> u64 { - v_to_chain_id(self.signature.v()) + fn max_fee_per_gas(&self) -> Option<&U256> { + None } - /// Returns the (cached) RLP-encoding of the transaction. - pub fn rlp_encoding(&self) -> &Bytes { + fn rlp_encoding(&self) -> &Bytes { self.rlp_encoding .get_or_init(|| alloy_rlp::encode(self).into()) } - /// Returns the (cached) hash of the transaction. - pub fn transaction_hash(&self) -> &B256 { + fn total_blob_gas(&self) -> Option { + None + } + + fn transaction_hash(&self) -> &B256 { self.hash.get_or_init(|| keccak256(alloy_rlp::encode(self))) } } @@ -87,6 +90,60 @@ impl PartialEq for Eip155 { } } +impl Transaction for Eip155 { + fn caller(&self) -> &Address { + self.signature.caller() + } + + fn gas_limit(&self) -> u64 { + self.gas_limit + } + + fn gas_price(&self) -> &U256 { + &self.gas_price + } + + fn kind(&self) -> TxKind { + self.kind + } + + fn value(&self) -> &U256 { + &self.value + } + + fn data(&self) -> &Bytes { + &self.input + } + + fn nonce(&self) -> u64 { + self.nonce + } + + fn chain_id(&self) -> Option { + Some(v_to_chain_id(self.signature.v())) + } + + fn access_list(&self) -> &[AccessListItem] { + &[] + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + None + } + + fn blob_hashes(&self) -> &[B256] { + &[] + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + None + } + + fn authorization_list(&self) -> Option<&AuthorizationList> { + None + } +} + /// Converts a V-value to a chain ID. pub(super) fn v_to_chain_id(v: u64) -> u64 { (v - 35) / 2 diff --git a/crates/edr_eth/src/transaction/signed/eip1559.rs b/crates/edr_eth/src/transaction/signed/eip1559.rs index cf151e545..c3d292ad7 100644 --- a/crates/edr_eth/src/transaction/signed/eip1559.rs +++ b/crates/edr_eth/src/transaction/signed/eip1559.rs @@ -1,13 +1,13 @@ use std::sync::OnceLock; use alloy_rlp::{Encodable as _, RlpDecodable, RlpEncodable}; -use revm_primitives::keccak256; +use revm_primitives::{keccak256, AuthorizationList}; use crate::{ signature::{self, Fakeable}, - transaction::{self, TxKind}, + transaction::{self, ExecutableTransaction, Transaction, TxKind}, utils::enveloped, - AccessList, Address, Bytes, B256, U256, + AccessList, AccessListItem, Address, Bytes, B256, U256, }; #[derive(Clone, Debug, Eq, RlpEncodable)] @@ -42,14 +42,21 @@ pub struct Eip1559 { impl Eip1559 { /// The type identifier for an EIP-1559 transaction. pub const TYPE: u8 = transaction::request::Eip1559::TYPE; +} - /// Returns the caller/signer of the transaction. - pub fn caller(&self) -> &Address { - self.signature.caller() +impl ExecutableTransaction for Eip1559 { + fn effective_gas_price(&self, block_base_fee: U256) -> Option { + Some( + self.max_fee_per_gas + .min(block_base_fee + self.max_priority_fee_per_gas), + ) } - /// Returns the (cached) RLP-encoding of the transaction. - pub fn rlp_encoding(&self) -> &Bytes { + fn max_fee_per_gas(&self) -> Option<&U256> { + Some(&self.max_fee_per_gas) + } + + fn rlp_encoding(&self) -> &Bytes { self.rlp_encoding.get_or_init(|| { let mut encoded = Vec::with_capacity(1 + self.length()); enveloped(Self::TYPE, self, &mut encoded); @@ -57,8 +64,11 @@ impl Eip1559 { }) } - /// Returns the (cached) hash of the transaction. - pub fn transaction_hash(&self) -> &B256 { + fn total_blob_gas(&self) -> Option { + None + } + + fn transaction_hash(&self) -> &B256 { self.hash.get_or_init(|| keccak256(self.rlp_encoding())) } } @@ -78,6 +88,60 @@ impl PartialEq for Eip1559 { } } +impl Transaction for Eip1559 { + fn caller(&self) -> &Address { + self.signature.caller() + } + + fn gas_limit(&self) -> u64 { + self.gas_limit + } + + fn gas_price(&self) -> &U256 { + &self.max_fee_per_gas + } + + fn kind(&self) -> TxKind { + self.kind + } + + fn value(&self) -> &U256 { + &self.value + } + + fn data(&self) -> &Bytes { + &self.input + } + + fn nonce(&self) -> u64 { + self.nonce + } + + fn chain_id(&self) -> Option { + Some(self.chain_id) + } + + fn access_list(&self) -> &[AccessListItem] { + &self.access_list.0 + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + Some(&self.max_priority_fee_per_gas) + } + + fn blob_hashes(&self) -> &[B256] { + &[] + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + None + } + + fn authorization_list(&self) -> Option<&AuthorizationList> { + None + } +} + #[derive(RlpDecodable)] struct Decodable { // The order of these fields determines decoding order. diff --git a/crates/edr_eth/src/transaction/signed/eip2930.rs b/crates/edr_eth/src/transaction/signed/eip2930.rs index 3924886c1..72fda5427 100644 --- a/crates/edr_eth/src/transaction/signed/eip2930.rs +++ b/crates/edr_eth/src/transaction/signed/eip2930.rs @@ -1,13 +1,13 @@ use std::sync::OnceLock; use alloy_rlp::{Encodable as _, RlpDecodable, RlpEncodable}; -use revm_primitives::keccak256; +use revm_primitives::{keccak256, AuthorizationList}; use crate::{ signature::{self, Fakeable}, - transaction::{self, TxKind}, + transaction::{self, ExecutableTransaction, Transaction, TxKind}, utils::enveloped, - AccessList, Address, Bytes, B256, U256, + AccessList, AccessListItem, Address, Bytes, B256, U256, }; #[derive(Clone, Debug, Eq, RlpEncodable)] @@ -41,14 +41,18 @@ pub struct Eip2930 { impl Eip2930 { /// The type identifier for an EIP-2930 transaction. pub const TYPE: u8 = transaction::request::Eip2930::TYPE; +} - /// Returns the caller/signer of the transaction. - pub fn caller(&self) -> &Address { - self.signature.caller() +impl ExecutableTransaction for Eip2930 { + fn effective_gas_price(&self, _block_base_fee: U256) -> Option { + None } - /// Returns the (cached) RLP-encoding of the transaction. - pub fn rlp_encoding(&self) -> &Bytes { + fn max_fee_per_gas(&self) -> Option<&U256> { + None + } + + fn rlp_encoding(&self) -> &Bytes { self.rlp_encoding.get_or_init(|| { let mut encoded = Vec::with_capacity(1 + self.length()); enveloped(Self::TYPE, self, &mut encoded); @@ -56,8 +60,11 @@ impl Eip2930 { }) } - /// Returns the (cached) hash of the transaction. - pub fn transaction_hash(&self) -> &B256 { + fn total_blob_gas(&self) -> Option { + None + } + + fn transaction_hash(&self) -> &B256 { self.hash.get_or_init(|| keccak256(self.rlp_encoding())) } } @@ -76,6 +83,60 @@ impl PartialEq for Eip2930 { } } +impl Transaction for Eip2930 { + fn caller(&self) -> &Address { + self.signature.caller() + } + + fn gas_limit(&self) -> u64 { + self.gas_limit + } + + fn gas_price(&self) -> &U256 { + &self.gas_price + } + + fn kind(&self) -> TxKind { + self.kind + } + + fn value(&self) -> &U256 { + &self.value + } + + fn data(&self) -> &Bytes { + &self.input + } + + fn nonce(&self) -> u64 { + self.nonce + } + + fn chain_id(&self) -> Option { + Some(self.chain_id) + } + + fn access_list(&self) -> &[AccessListItem] { + &self.access_list.0 + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + None + } + + fn blob_hashes(&self) -> &[B256] { + &[] + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + None + } + + fn authorization_list(&self) -> Option<&AuthorizationList> { + None + } +} + #[derive(RlpDecodable)] struct Decodable { // The order of these fields determines decoding order. diff --git a/crates/edr_eth/src/transaction/signed/eip4844.rs b/crates/edr_eth/src/transaction/signed/eip4844.rs index c13ddd083..fd79c6164 100644 --- a/crates/edr_eth/src/transaction/signed/eip4844.rs +++ b/crates/edr_eth/src/transaction/signed/eip4844.rs @@ -1,13 +1,13 @@ use std::sync::OnceLock; use alloy_rlp::{Encodable as _, RlpDecodable, RlpEncodable}; -use revm_primitives::{keccak256, GAS_PER_BLOB}; +use revm_primitives::{keccak256, AuthorizationList, GAS_PER_BLOB}; use crate::{ signature::{self, Fakeable}, - transaction, + transaction::{self, ExecutableTransaction, Transaction, TxKind}, utils::enveloped, - AccessList, Address, Bytes, B256, U256, + AccessList, AccessListItem, Address, Bytes, B256, U256, }; #[derive(Clone, Debug, Eq, RlpEncodable)] @@ -44,18 +44,21 @@ pub struct Eip4844 { impl Eip4844 { /// The type identifier for an EIP-4844 transaction. pub const TYPE: u8 = transaction::request::Eip4844::TYPE; +} - /// Returns the caller/signer of the transaction. - pub fn caller(&self) -> &Address { - self.signature.caller() +impl ExecutableTransaction for Eip4844 { + fn effective_gas_price(&self, block_base_fee: U256) -> Option { + Some( + self.max_fee_per_gas + .min(block_base_fee + self.max_priority_fee_per_gas), + ) } - pub fn nonce(&self) -> &u64 { - &self.nonce + fn max_fee_per_gas(&self) -> Option<&U256> { + Some(&self.max_fee_per_gas) } - /// Returns the (cached) RLP-encoding of the transaction. - pub fn rlp_encoding(&self) -> &Bytes { + fn rlp_encoding(&self) -> &Bytes { self.rlp_encoding.get_or_init(|| { let mut encoded = Vec::with_capacity(1 + self.length()); enveloped(Self::TYPE, self, &mut encoded); @@ -63,14 +66,12 @@ impl Eip4844 { }) } - /// Returns the (cached) hash of the transaction. - pub fn transaction_hash(&self) -> &B256 { - self.hash.get_or_init(|| keccak256(self.rlp_encoding())) + fn total_blob_gas(&self) -> Option { + Some(total_blob_gas(self)) } - /// Total blob gas used by the transaction. - pub fn total_blob_gas(&self) -> u64 { - GAS_PER_BLOB * self.blob_hashes.len() as u64 + fn transaction_hash(&self) -> &B256 { + self.hash.get_or_init(|| keccak256(self.rlp_encoding())) } } @@ -91,6 +92,60 @@ impl PartialEq for Eip4844 { } } +impl Transaction for Eip4844 { + fn caller(&self) -> &Address { + self.signature.caller() + } + + fn gas_limit(&self) -> u64 { + self.gas_limit + } + + fn gas_price(&self) -> &U256 { + &self.max_fee_per_gas + } + + fn kind(&self) -> TxKind { + TxKind::Call(self.to) + } + + fn value(&self) -> &U256 { + &self.value + } + + fn data(&self) -> &Bytes { + &self.input + } + + fn nonce(&self) -> u64 { + self.nonce + } + + fn chain_id(&self) -> Option { + Some(self.chain_id) + } + + fn access_list(&self) -> &[AccessListItem] { + &self.access_list.0 + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + Some(&self.max_priority_fee_per_gas) + } + + fn blob_hashes(&self) -> &[B256] { + &self.blob_hashes + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + Some(&self.max_fee_per_blob_gas) + } + + fn authorization_list(&self) -> Option<&AuthorizationList> { + None + } +} + #[derive(RlpDecodable)] struct Decodable { // The order of these fields determines decoding order. @@ -153,6 +208,11 @@ impl From<&Decodable> for transaction::request::Eip4844 { } } +/// Total blob gas used by the transaction. +pub fn total_blob_gas(transaction: &Eip4844) -> u64 { + GAS_PER_BLOB * (transaction.blob_hashes.len() as u64) +} + #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/crates/edr_eth/src/transaction/signed/legacy.rs b/crates/edr_eth/src/transaction/signed/legacy.rs index 4d3df151b..81818af29 100644 --- a/crates/edr_eth/src/transaction/signed/legacy.rs +++ b/crates/edr_eth/src/transaction/signed/legacy.rs @@ -1,12 +1,12 @@ use std::sync::OnceLock; use alloy_rlp::{RlpDecodable, RlpEncodable}; -use revm_primitives::keccak256; +use revm_primitives::{keccak256, AuthorizationList}; use crate::{ signature::{self, Fakeable}, - transaction::{self, TxKind}, - Address, Bytes, B256, U256, + transaction::{self, ExecutableTransaction, Transaction, TxKind}, + AccessListItem, Address, Bytes, B256, U256, }; #[derive(Clone, Debug, Eq, RlpEncodable)] @@ -36,20 +36,27 @@ pub struct Legacy { impl Legacy { /// The type identifier for a pre-EIP-155 legacy transaction. pub const TYPE: u8 = transaction::request::Legacy::TYPE; +} - /// Returns the caller/signer of the transaction. - pub fn caller(&self) -> &Address { - self.signature.caller() +impl ExecutableTransaction for Legacy { + fn effective_gas_price(&self, _block_base_fee: U256) -> Option { + None } - /// Returns the (cached) RLP-encoding of the transaction. - pub fn rlp_encoding(&self) -> &Bytes { + fn max_fee_per_gas(&self) -> Option<&U256> { + None + } + + fn rlp_encoding(&self) -> &Bytes { self.rlp_encoding .get_or_init(|| alloy_rlp::encode(self).into()) } - /// Returns the (cached) hash of the transaction. - pub fn transaction_hash(&self) -> &B256 { + fn total_blob_gas(&self) -> Option { + None + } + + fn transaction_hash(&self) -> &B256 { self.hash.get_or_init(|| keccak256(self.rlp_encoding())) } } @@ -66,6 +73,60 @@ impl PartialEq for Legacy { } } +impl Transaction for Legacy { + fn caller(&self) -> &Address { + self.signature.caller() + } + + fn gas_limit(&self) -> u64 { + self.gas_limit + } + + fn gas_price(&self) -> &U256 { + &self.gas_price + } + + fn kind(&self) -> TxKind { + self.kind + } + + fn value(&self) -> &U256 { + &self.value + } + + fn data(&self) -> &Bytes { + &self.input + } + + fn nonce(&self) -> u64 { + self.nonce + } + + fn chain_id(&self) -> Option { + None + } + + fn access_list(&self) -> &[AccessListItem] { + &[] + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + None + } + + fn blob_hashes(&self) -> &[B256] { + &[] + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + None + } + + fn authorization_list(&self) -> Option<&AuthorizationList> { + None + } +} + /// A transaction that is either a legacy transaction or an EIP-155 /// transaction. This is used to decode `super::Signed`, as /// their decoding format is the same. diff --git a/crates/edr_evm/src/block.rs b/crates/edr_evm/src/block.rs index b3a659214..f86a8679a 100644 --- a/crates/edr_evm/src/block.rs +++ b/crates/edr_evm/src/block.rs @@ -1,13 +1,16 @@ mod builder; mod local; mod remote; +/// Block-related transaction types. +pub mod transaction; use std::{fmt::Debug, sync::Arc}; use auto_impl::auto_impl; +use derive_where::derive_where; use edr_eth::{ block::{self, BlobGas, Header}, - transaction::SignedTransaction, + transaction::ExecutableTransaction, withdrawal::Withdrawal, B256, U256, }; @@ -150,7 +153,7 @@ impl TryFrom { /// The block pub block: Arc>, @@ -158,17 +161,6 @@ pub struct BlockAndTotalDifficulty { pub total_difficulty: Option, } -impl Clone - for BlockAndTotalDifficulty -{ - fn clone(&self) -> Self { - Self { - block: self.block.clone(), - total_difficulty: self.total_difficulty, - } - } -} - impl From> for edr_rpc_eth::Block { diff --git a/crates/edr_evm/src/block/builder.rs b/crates/edr_evm/src/block/builder.rs index 0f065b210..c77955429 100644 --- a/crates/edr_evm/src/block/builder.rs +++ b/crates/edr_evm/src/block/builder.rs @@ -8,7 +8,7 @@ use edr_eth::{ log::ExecutionLog, receipt::{ExecutionReceiptBuilder as _, Receipt as _, TransactionReceipt}, result::InvalidTransaction, - transaction::SignedTransaction as _, + transaction::ExecutableTransaction as _, trie::{ordered_trie_root, KECCAK_NULL_RLP}, withdrawal::Withdrawal, Address, Bloom, U256, @@ -166,18 +166,19 @@ impl BlockBuilder { pub fn header(&self) -> &PartialHeader { &self.header } +} +impl BlockBuilder { /// Finalizes the block, returning the block and the callers of the /// transactions. #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] - pub fn finalize( + pub fn finalize( mut self, state: &mut StateT, rewards: Vec<(Address, U256)>, ) -> Result, StateErrorT> where StateT: SyncState + ?Sized, - StateErrorT: Debug + Send, { for (address, reward) in rewards { if reward > U256::ZERO { @@ -248,7 +249,7 @@ where { /// Adds a pending transaction to #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] - pub fn add_transaction<'blockchain, 'evm, BlockchainErrorT, DebugDataT, StateT, StateErrorT>( + pub fn add_transaction<'blockchain, 'evm, DebugDataT, StateT, BlockchainErrorT, StateErrorT>( &mut self, blockchain: &'blockchain dyn SyncBlockchain, state: StateT, @@ -264,9 +265,7 @@ where > where 'blockchain: 'evm, - BlockchainErrorT: Debug + Send, StateT: StateRef + DatabaseCommit + StateDebug, - StateErrorT: Debug + Send, { // The transaction's gas limit cannot be greater than the remaining gas in the // block diff --git a/crates/edr_evm/src/block/local.rs b/crates/edr_evm/src/block/local.rs index aed0a73be..1deb62cc9 100644 --- a/crates/edr_evm/src/block/local.rs +++ b/crates/edr_evm/src/block/local.rs @@ -6,7 +6,7 @@ use edr_eth::{ block::{self, Header, PartialHeader}, log::{ExecutionLog, FilterLog, FullBlockLog, ReceiptLog}, receipt::{BlockReceipt, MapReceiptLogs as _, TransactionReceipt}, - transaction::SignedTransaction, + transaction::ExecutableTransaction, trie, withdrawal::Withdrawal, SpecId, B256, @@ -69,7 +69,7 @@ impl LocalBlock { let ommer_hashes = ommers.iter().map(Header::hash).collect::>(); let ommers_hash = keccak256(alloy_rlp::encode(&ommers)); let transactions_root = - trie::ordered_trie_root(transactions.iter().map(SignedTransaction::rlp_encoding)); + trie::ordered_trie_root(transactions.iter().map(ExecutableTransaction::rlp_encoding)); let withdrawals_root = withdrawals .as_ref() diff --git a/crates/edr_evm/src/block/remote.rs b/crates/edr_evm/src/block/remote.rs index c9a7967b7..bc5560033 100644 --- a/crates/edr_evm/src/block/remote.rs +++ b/crates/edr_evm/src/block/remote.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, OnceLock}; use derive_where::derive_where; use edr_eth::{ - block::Header, transaction::SignedTransaction as _, withdrawal::Withdrawal, B256, U256, + block::Header, transaction::ExecutableTransaction as _, withdrawal::Withdrawal, B256, U256, }; use edr_rpc_eth::client::EthRpcClient; use tokio::runtime; diff --git a/crates/edr_evm/src/block/transaction.rs b/crates/edr_evm/src/block/transaction.rs new file mode 100644 index 000000000..12bc16c35 --- /dev/null +++ b/crates/edr_evm/src/block/transaction.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use derive_where::derive_where; +use edr_eth::{chain_spec::L1ChainSpec, transaction::SignedTransaction as _, SpecId}; +use edr_rpc_eth::RpcTypeFrom; + +use super::SyncBlock; +use crate::{blockchain::BlockchainError, chain_spec::ChainSpec}; + +/// The result returned by requesting a transaction. +#[derive_where(Clone, Debug; ChainSpecT::Transaction)] +pub struct TransactionAndBlock { + /// The transaction. + pub transaction: ChainSpecT::Transaction, + /// Block data in which the transaction is found if it has been mined. + pub block_data: Option>, + /// Whether the transaction is pending + pub is_pending: bool, +} + +/// Block metadata for a transaction. +#[derive_where(Clone, Debug)] +pub struct BlockDataForTransaction { + /// The block in which the transaction is found. + pub block: Arc>>, + /// The index of the transaction in the block. + pub transaction_index: u64, +} + +impl RpcTypeFrom> for edr_rpc_eth::TransactionWithSignature { + type Hardfork = SpecId; + + fn rpc_type_from(value: &TransactionAndBlock, hardfork: Self::Hardfork) -> Self { + let (header, transaction_index) = value + .block_data + .as_ref() + .map( + |BlockDataForTransaction { + block, + transaction_index, + }| (block.header(), *transaction_index), + ) + .unzip(); + + let transaction = edr_rpc_eth::Transaction::new( + &value.transaction, + header, + transaction_index, + value.is_pending, + hardfork, + ); + let signature = value.transaction.signature(); + + edr_rpc_eth::TransactionWithSignature::new( + transaction, + signature.r(), + signature.s(), + signature.v(), + signature.y_parity(), + ) + } +} diff --git a/crates/edr_evm/src/blockchain/storage/sparse.rs b/crates/edr_evm/src/blockchain/storage/sparse.rs index c6949c6d8..75106758b 100644 --- a/crates/edr_evm/src/blockchain/storage/sparse.rs +++ b/crates/edr_evm/src/blockchain/storage/sparse.rs @@ -4,7 +4,7 @@ use derive_where::derive_where; use edr_eth::{ log::{matches_address_filter, matches_topics_filter, FilterLog}, receipt::{BlockReceipt, Receipt as _}, - transaction::SignedTransaction, + transaction::ExecutableTransaction, Address, B256, U256, }; use revm::primitives::{hash_map::OccupiedError, HashMap, HashSet}; @@ -259,7 +259,7 @@ where if let Some(block) = storage.block_by_number(block_number) { let receipts = block.transaction_receipts()?; for receipt in receipts { - let filtered_logs = receipt.logs().iter().filter(|log| { + let filtered_logs = receipt.transaction_logs().iter().filter(|log| { matches_address_filter(&log.address, &addresses) && matches_topics_filter(log.topics(), topics_filter) }); diff --git a/crates/edr_evm/src/chain_spec.rs b/crates/edr_evm/src/chain_spec.rs index c5e30c8b2..ae3f93ec5 100644 --- a/crates/edr_evm/src/chain_spec.rs +++ b/crates/edr_evm/src/chain_spec.rs @@ -1,20 +1,23 @@ use std::fmt::Debug; use edr_eth::{ - block::{BlobGas, PartialHeader}, + block::{self, BlobGas, PartialHeader}, chain_spec::{EthHeaderConstants, L1ChainSpec}, env::{BlobExcessGasAndPrice, BlockEnv}, log::{ExecutionLog, FilterLog}, receipt::{ExecutionReceiptBuilder, MapReceiptLogs}, - transaction::SignedTransaction, + result::InvalidTransaction, + transaction::ExecutableTransaction, SpecId, B256, U256, }; -use edr_rpc_eth::{spec::RpcSpec, TransactionConversionError}; +use edr_rpc_eth::{spec::RpcSpec, RpcTypeFrom, TransactionConversionError}; +use revm::primitives::TransactionValidation; pub use revm::EvmWiring; use crate::{ + block::transaction::TransactionAndBlock, hardfork::{self, Activations}, - transaction::remote::EthRpcTransaction, + transaction::{remote::EthRpcTransaction, Transaction, TransactionError, TransactionType}, BlockReceipt, EthBlockData, EthRpcBlock, RemoteBlockConversionError, }; @@ -25,13 +28,15 @@ pub trait ChainSpec: alloy_rlp::Encodable + EthHeaderConstants + EvmWiring< - Block: BlockEnvConstructor, + Block: BlockEnvConstructor + BlockEnvConstructor, Transaction: alloy_rlp::Encodable + Clone + Debug + PartialEq + Eq - + SignedTransaction + + ExecutableTransaction + + Transaction + + TransactionType + TryFrom< ::RpcTransaction, Error = Self::RpcTransactionConversionError, @@ -43,8 +48,11 @@ pub trait ChainSpec: EthBlockData, Error = Self::RpcBlockConversionError, >, - RpcReceipt: Debug + TryInto, Error = Self::RpcReceiptConversionError>, - RpcTransaction: EthRpcTransaction, + RpcReceipt: Debug + + RpcTypeFrom, Hardfork = Self::Hardfork> + + TryInto, Error = Self::RpcReceiptConversionError>, + RpcTransaction: EthRpcTransaction + + RpcTypeFrom, Hardfork = Self::Hardfork>, > + RpcSpec< ExecutionReceipt: alloy_rlp::Encodable + MapReceiptLogs< @@ -71,6 +79,14 @@ pub trait ChainSpec: /// transaction. type RpcTransactionConversionError: std::error::Error; + /// Casts a transaction validation error into a `TransactionError`. + /// + /// This is implemented as an associated function to avoid problems when + /// implementing type conversions for third-party types. + fn cast_transaction_error( + error: ::ValidationError, + ) -> TransactionError; + /// Returns the hardfork activations corresponding to the provided chain ID, /// if it is associated with this chain specification. fn chain_hardfork_activations(chain_id: u64) -> Option<&'static Activations>; @@ -81,12 +97,12 @@ pub trait ChainSpec: } /// A trait for constructing a block a [`PartialHeader`] into an EVM block. -pub trait BlockEnvConstructor { +pub trait BlockEnvConstructor { /// Converts the instance into an EVM block. - fn new_block_env(header: &PartialHeader, hardfork: ChainSpecT::Hardfork) -> Self; + fn new_block_env(header: &HeaderT, hardfork: ChainSpecT::Hardfork) -> Self; } -impl BlockEnvConstructor for BlockEnv { +impl BlockEnvConstructor for BlockEnv { fn new_block_env(header: &PartialHeader, hardfork: SpecId) -> Self { Self { number: U256::from(header.number), @@ -108,18 +124,52 @@ impl BlockEnvConstructor for BlockEnv { } } +impl BlockEnvConstructor for BlockEnv { + fn new_block_env(header: &block::Header, hardfork: SpecId) -> Self { + Self { + number: U256::from(header.number), + coinbase: header.beneficiary, + timestamp: U256::from(header.timestamp), + difficulty: header.difficulty, + basefee: header.base_fee_per_gas.unwrap_or(U256::ZERO), + gas_limit: U256::from(header.gas_limit), + prevrandao: if hardfork >= SpecId::MERGE { + Some(header.mix_hash) + } else { + None + }, + blob_excess_gas_and_price: header + .blob_gas + .as_ref() + .map(|BlobGas { excess_gas, .. }| BlobExcessGasAndPrice::new(*excess_gas)), + } + } +} + /// A supertrait for [`ChainSpec`] that is safe to send between threads. pub trait SyncChainSpec: - ChainSpec: Send + Sync, Transaction: Send + Sync> - + Send + ChainSpec< + ExecutionReceipt: Send + Sync, + HaltReason: Send + Sync, + Hardfork: Send + Sync, + RpcBlockConversionError: Send + Sync, + RpcReceiptConversionError: Send + Sync, + Transaction: TransactionValidation + Send + Sync, + > + Send + Sync + 'static { } impl SyncChainSpec for ChainSpecT where - ChainSpecT: ChainSpec: Send + Sync, Transaction: Send + Sync> - + Send + ChainSpecT: ChainSpec< + ExecutionReceipt: Send + Sync, + HaltReason: Send + Sync, + Hardfork: Send + Sync, + RpcBlockConversionError: Send + Sync, + RpcReceiptConversionError: Send + Sync, + Transaction: TransactionValidation + Send + Sync, + > + Send + Sync + 'static { @@ -131,6 +181,17 @@ impl ChainSpec for L1ChainSpec { type RpcReceiptConversionError = edr_rpc_eth::receipt::ConversionError; type RpcTransactionConversionError = TransactionConversionError; + fn cast_transaction_error( + error: ::ValidationError, + ) -> TransactionError { + match error { + InvalidTransaction::LackOfFundForMaxFee { fee, balance } => { + TransactionError::LackOfFundForMaxFee { fee, balance } + } + remainder => TransactionError::InvalidTransaction(remainder), + } + } + fn chain_hardfork_activations(chain_id: u64) -> Option<&'static Activations> { hardfork::l1::chain_hardfork_activations(chain_id) } diff --git a/crates/edr_evm/src/debug_trace.rs b/crates/edr_evm/src/debug_trace.rs index 4645be4d8..259e5006a 100644 --- a/crates/edr_evm/src/debug_trace.rs +++ b/crates/edr_evm/src/debug_trace.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, fmt::Debug, marker::PhantomData, sync::Arc}; -use edr_eth::{transaction::SignedTransaction as _, utils::u256_to_padded_hex, B256}; +use edr_eth::{transaction::ExecutableTransaction as _, utils::u256_to_padded_hex, B256}; use revm::{ db::DatabaseComponents, handler::{register::EvmHandler, CfgEnvWithEvmWiring}, diff --git a/crates/edr_evm/src/lib.rs b/crates/edr_evm/src/lib.rs index 95cf65e8c..cfd295491 100644 --- a/crates/edr_evm/src/lib.rs +++ b/crates/edr_evm/src/lib.rs @@ -29,7 +29,8 @@ pub mod state; /// Types used for tracing EVM calls pub mod trace; -mod block; +/// Types for Ethereum blocks. +pub mod block; /// Types for chain specification. pub mod chain_spec; pub(crate) mod collections; diff --git a/crates/edr_evm/src/mempool.rs b/crates/edr_evm/src/mempool.rs index df69c5073..c0dc9ea22 100644 --- a/crates/edr_evm/src/mempool.rs +++ b/crates/edr_evm/src/mempool.rs @@ -2,7 +2,7 @@ use std::{cmp::Ordering, fmt::Debug, num::NonZeroU64}; use derive_where::derive_where; use edr_eth::{ - transaction::{upfront_cost, SignedTransaction}, + transaction::{upfront_cost, ExecutableTransaction}, Address, B256, U256, }; use indexmap::{map::Entry, IndexMap}; @@ -173,7 +173,7 @@ impl OrderedTransaction { } /// The mempool contains transactions pending inclusion in the blockchain. -#[derive(Clone, Debug)] +#[derive_where(Clone, Debug; ChainSpecT::Transaction)] pub struct MemPool { /// The block's gas limit block_gas_limit: NonZeroU64, @@ -396,7 +396,7 @@ impl MemPool { S::Error: Debug, { fn is_valid_tx( - transaction: &impl SignedTransaction, + transaction: &impl Transaction, block_gas_limit: NonZeroU64, sender: &AccountInfo, ) -> bool { diff --git a/crates/edr_evm/src/miner.rs b/crates/edr_evm/src/miner.rs index 0ea9ec9db..414806bdb 100644 --- a/crates/edr_evm/src/miner.rs +++ b/crates/edr_evm/src/miner.rs @@ -3,7 +3,7 @@ use std::{cmp::Ordering, fmt::Debug, sync::Arc}; use edr_eth::{ block::{calculate_next_base_fee_per_blob_gas, BlockOptions}, signature::SignatureError, - transaction::{SignedTransaction as _, Transaction}, + transaction::{ExecutableTransaction as _, Transaction}, U256, }; use revm::{ @@ -351,10 +351,10 @@ where if let Some(base_fee_per_gas) = options.base_fee { if let Some(max_fee_per_gas) = transaction.max_fee_per_gas() { - if max_fee_per_gas < base_fee_per_gas { + if *max_fee_per_gas < base_fee_per_gas { return Err(MineTransactionError::MaxFeePerGasTooLow { expected: base_fee_per_gas, - actual: max_fee_per_gas, + actual: *max_fee_per_gas, }); } } else { diff --git a/crates/edr_evm/src/runtime.rs b/crates/edr_evm/src/runtime.rs index 3dfe8639e..0e7c2a2f6 100644 --- a/crates/edr_evm/src/runtime.rs +++ b/crates/edr_evm/src/runtime.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use edr_eth::{ db::{DatabaseComponents, StateRef}, result::{ExecutionResult, InvalidTransaction, ResultAndState}, - transaction::{SignedTransaction as _, TransactionValidation}, + transaction::{ExecutableTransaction as _, TransactionValidation}, Address, HashMap, Precompile, SpecId, }; use revm::{ diff --git a/crates/edr_evm/src/test_utils.rs b/crates/edr_evm/src/test_utils.rs index c73ccf907..2d602595f 100644 --- a/crates/edr_evm/src/test_utils.rs +++ b/crates/edr_evm/src/test_utils.rs @@ -323,7 +323,11 @@ pub async fn run_full_block< replay_block.transactions()[expected.transaction_index as usize] ); if expected.logs_bloom() != actual.logs_bloom() { - for (expected, actual) in expected.logs().iter().zip(actual.logs().iter()) { + for (expected, actual) in expected + .transaction_logs() + .iter() + .zip(actual.transaction_logs().iter()) + { debug_assert_eq!( expected.inner.address, actual.inner.address, diff --git a/crates/edr_evm/src/trace.rs b/crates/edr_evm/src/trace.rs index 5410a8050..9cb1455e2 100644 --- a/crates/edr_evm/src/trace.rs +++ b/crates/edr_evm/src/trace.rs @@ -193,7 +193,7 @@ pub struct AfterMessage { #[derive(Debug)] #[derive_where(Clone; ChainSpecT::HaltReason)] #[derive_where(Default)] -pub struct Trace { +pub struct Trace { // /// The individual steps of the call // pub steps: Vec, /// Messages diff --git a/crates/edr_evm/src/transaction.rs b/crates/edr_evm/src/transaction.rs index 57a7fd179..492360b1e 100644 --- a/crates/edr_evm/src/transaction.rs +++ b/crates/edr_evm/src/transaction.rs @@ -4,9 +4,10 @@ pub mod remote; use std::fmt::Debug; +use derive_where::derive_where; // Re-export the transaction types from `edr_eth`. pub use edr_eth::transaction::*; -use edr_eth::{SpecId, U256}; +use edr_eth::{result::InvalidHeader, SpecId, U256}; use revm::{ db::DatabaseComponentError, interpreter::gas::validate_initial_tx_gas, @@ -14,9 +15,11 @@ use revm::{ }; pub use self::detailed::*; +use crate::chain_spec::ChainSpec; /// Invalid transaction error #[derive(thiserror::Error)] +#[derive_where(Debug; ::ValidationError, BlockchainErrorT, StateErrorT)] pub enum TransactionError where ChainSpecT: revm::primitives::EvmWiring, @@ -32,10 +35,19 @@ where Eip1559Unsupported, /// Invalid block header #[error(transparent)] - InvalidHeader(revm::primitives::InvalidHeader), + InvalidHeader(InvalidHeader), /// Corrupt transaction data #[error(transparent)] InvalidTransaction(::ValidationError), + /// Transaction account does not have enough amount of ether to cover + /// transferred value and gas_limit*gas_price. + #[error("Sender doesn't have enough funds to send tx. The max upfront cost is: {fee} and the sender's balance is: {balance}.")] + LackOfFundForMaxFee { + /// The max upfront cost of the transaction + fee: Box, + /// The sender's balance + balance: Box, + }, /// Precompile errors #[error("{0}")] Precompile(String), @@ -44,42 +56,17 @@ where State(StateErrorT), } -impl Debug - for TransactionError -where - ChainSpecT: - revm::primitives::EvmWiring>, - BlockchainErrorT: Debug, - StateErrorT: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Blockchain(arg0) => f.debug_tuple("Blockchain").field(arg0).finish(), - Self::Custom(arg0) => f.debug_tuple("Custom").field(arg0).finish(), - Self::Eip1559Unsupported => write!(f, "Eip1559Unsupported"), - Self::InvalidHeader(arg0) => f.debug_tuple("InvalidHeader").field(arg0).finish(), - Self::InvalidTransaction(arg0) => { - f.debug_tuple("InvalidTransaction").field(arg0).finish() - } - Self::Precompile(arg0) => f.debug_tuple("Precompile").field(arg0).finish(), - Self::State(arg0) => f.debug_tuple("State").field(arg0).finish(), - } - } -} - impl From, ChainSpecT>> for TransactionError where - ChainSpecT: revm::primitives::EvmWiring, - BlockchainErrorT: Debug + Send, - StateErrorT: Debug + Send, + ChainSpecT: ChainSpec, { fn from( error: EVMErrorForChain, ChainSpecT>, ) -> Self { match error { - EVMError::Transaction(error) => Self::InvalidTransaction(error), + EVMError::Transaction(error) => ChainSpecT::cast_transaction_error(error), EVMError::Header(error) => Self::InvalidHeader(error), EVMError::Database(DatabaseComponentError::State(e)) => Self::State(e), EVMError::Database(DatabaseComponentError::BlockHash(e)) => Self::Blockchain(e), @@ -106,7 +93,7 @@ pub enum CreationError { } /// Validates the transaction. -pub fn validate( +pub fn validate( transaction: TransactionT, spec_id: SpecId, ) -> Result { diff --git a/crates/edr_evm/src/transaction/remote.rs b/crates/edr_evm/src/transaction/remote.rs index e9bc89acb..1d0d7127e 100644 --- a/crates/edr_evm/src/transaction/remote.rs +++ b/crates/edr_evm/src/transaction/remote.rs @@ -7,7 +7,7 @@ pub trait EthRpcTransaction { fn block_hash(&self) -> Option<&B256>; } -impl EthRpcTransaction for edr_rpc_eth::Transaction { +impl EthRpcTransaction for edr_rpc_eth::TransactionWithSignature { fn block_hash(&self) -> Option<&B256> { self.block_hash.as_ref() } diff --git a/crates/edr_evm/tests/blockchain.rs b/crates/edr_evm/tests/blockchain.rs index 47d0e85ef..9cae4d902 100644 --- a/crates/edr_evm/tests/blockchain.rs +++ b/crates/edr_evm/tests/blockchain.rs @@ -9,7 +9,7 @@ use edr_eth::{ log::{ExecutionLog, FilterLog}, receipt::{self, ExecutionReceiptBuilder, Receipt as _, TransactionReceipt}, result::{ExecutionResult, Output, SuccessReason}, - transaction::SignedTransaction as _, + transaction::ExecutableTransaction as _, Address, Bytes, HashSet, SpecId, B256, U256, }; use edr_evm::{ @@ -540,9 +540,9 @@ async fn logs_local() -> anyhow::Result<()> { &[], )?; - assert_eq_logs(&filtered_logs, transaction_receipt.logs()); + assert_eq_logs(&filtered_logs, transaction_receipt.transaction_logs()); - let logs = transaction_receipt.logs().iter(); + let logs = transaction_receipt.transaction_logs().iter(); let DummyBlockAndTransaction { block: two, transaction_receipt, @@ -550,7 +550,7 @@ async fn logs_local() -> anyhow::Result<()> { } = insert_dummy_block_with_transaction(blockchain.as_mut())?; let logs: Vec = logs - .chain(transaction_receipt.logs().iter()) + .chain(transaction_receipt.transaction_logs().iter()) .cloned() .collect(); diff --git a/crates/edr_evm/tests/mem_pool.rs b/crates/edr_evm/tests/mem_pool.rs index 31eeb2ae5..83be5432f 100644 --- a/crates/edr_evm/tests/mem_pool.rs +++ b/crates/edr_evm/tests/mem_pool.rs @@ -2,7 +2,7 @@ use std::num::NonZeroU64; -use edr_eth::{transaction::SignedTransaction, AccountInfo, Address, U256}; +use edr_eth::{transaction::ExecutableTransaction, AccountInfo, Address, U256}; use edr_evm::{ state::{AccountModifierFn, StateDebug}, test_utils::{ diff --git a/crates/edr_evm/tests/transaction.rs b/crates/edr_evm/tests/transaction.rs index 28f177b45..2fe1bbe34 100644 --- a/crates/edr_evm/tests/transaction.rs +++ b/crates/edr_evm/tests/transaction.rs @@ -10,7 +10,7 @@ mod alchemy { async fn []() { use edr_eth::{ chain_spec::L1ChainSpec, - transaction::{self, SignedTransaction as _}, + transaction::{self, ExecutableTransaction as _}, PreEip1898BlockSpec, B256 }; diff --git a/crates/edr_napi/src/logger.rs b/crates/edr_napi/src/logger.rs index d9521c579..44f979227 100644 --- a/crates/edr_napi/src/logger.rs +++ b/crates/edr_napi/src/logger.rs @@ -4,7 +4,7 @@ use ansi_term::{Color, Style}; use edr_eth::{ chain_spec::L1ChainSpec, result::ExecutionResult, - transaction::{self, SignedTransaction}, + transaction::{self, ExecutableTransaction}, Bytes, B256, U256, }; use edr_evm::{ @@ -140,7 +140,7 @@ impl edr_provider::Logger for Logger { &mut self, spec_id: edr_eth::SpecId, transaction: &transaction::Signed, - result: &edr_provider::CallResult, + result: &edr_provider::CallResult, ) -> Result<(), Box> { self.collector.log_call(spec_id, transaction, result); @@ -151,7 +151,7 @@ impl edr_provider::Logger for Logger { &mut self, spec_id: edr_eth::SpecId, transaction: &transaction::Signed, - failure: &edr_provider::EstimateGasFailure, + failure: &edr_provider::EstimateGasFailure, ) -> Result<(), Box> { self.collector .log_estimate_gas(spec_id, transaction, failure); @@ -196,7 +196,7 @@ impl edr_provider::Logger for Logger { fn print_method_logs( &mut self, method: &str, - error: Option<&ProviderError>, + error: Option<&ProviderError>, ) -> Result<(), Box> { if let Some(error) = error { self.collector.state = LoggingState::Empty; @@ -344,7 +344,7 @@ impl LogCollector { &mut self, spec_id: edr_eth::SpecId, transaction: &transaction::Signed, - result: &edr_provider::CallResult, + result: &edr_provider::CallResult, ) { let edr_provider::CallResult { console_log_inputs, @@ -379,7 +379,7 @@ impl LogCollector { &mut self, spec_id: edr_eth::SpecId, transaction: &transaction::Signed, - result: &edr_provider::EstimateGasFailure, + result: &edr_provider::EstimateGasFailure, ) { let edr_provider::EstimateGasFailure { console_log_inputs, @@ -406,7 +406,7 @@ impl LogCollector { }); } - fn log_transaction_failure(&mut self, failure: &edr_provider::TransactionFailure) { + fn log_transaction_failure(&mut self, failure: &edr_provider::TransactionFailure) { let is_revert_error = matches!( failure.reason, edr_provider::TransactionFailureReason::Revert(_) diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 6d03343ca..2f069ec00 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -20,7 +20,7 @@ use crate::{ /// A JSON-RPC provider for Ethereum. #[napi] pub struct Provider { - provider: Arc, + provider: Arc>, runtime: runtime::Handle, #[cfg(feature = "scenarios")] scenario_file: Option>, diff --git a/crates/edr_napi/src/scenarios.rs b/crates/edr_napi/src/scenarios.rs index 9fa8af1a5..3ea4cc278 100644 --- a/crates/edr_napi/src/scenarios.rs +++ b/crates/edr_napi/src/scenarios.rs @@ -48,7 +48,7 @@ pub(crate) async fn scenario_file( pub(crate) async fn write_request( scenario_file: &Mutex, - request: &ProviderRequest, + request: &ProviderRequest, ) -> napi::Result<()> { let mut line = serde_json::to_string(request)?; line.push('\n'); diff --git a/crates/edr_napi/src/subscribe.rs b/crates/edr_napi/src/subscribe.rs index 476e5747c..074f64446 100644 --- a/crates/edr_napi/src/subscribe.rs +++ b/crates/edr_napi/src/subscribe.rs @@ -1,4 +1,4 @@ -use edr_eth::B256; +use edr_eth::{chain_spec::L1ChainSpec, B256}; use napi::{ bindgen_prelude::BigInt, threadsafe_function::{ @@ -10,14 +10,14 @@ use napi_derive::napi; #[derive(Clone)] pub struct SubscriberCallback { - inner: ThreadsafeFunction, + inner: ThreadsafeFunction, ErrorStrategy::Fatal>, } impl SubscriberCallback { pub fn new(env: &Env, subscription_event_callback: JsFunction) -> napi::Result { let mut callback = subscription_event_callback.create_threadsafe_function( 0, - |ctx: ThreadSafeCallContext| { + |ctx: ThreadSafeCallContext>| { // SubscriptionEvent let mut event = ctx.env.create_object()?; @@ -49,7 +49,7 @@ impl SubscriberCallback { Ok(Self { inner: callback }) } - pub fn call(&self, event: edr_provider::SubscriptionEvent) { + pub fn call(&self, event: edr_provider::SubscriptionEvent) { // This is blocking because it's important that the subscription events are // in-order self.inner.call(event, ThreadsafeFunctionCallMode::Blocking); diff --git a/crates/edr_optimism/src/eip2718.rs b/crates/edr_optimism/src/eip2718.rs index 238ea19f5..417e6bb67 100644 --- a/crates/edr_optimism/src/eip2718.rs +++ b/crates/edr_optimism/src/eip2718.rs @@ -127,8 +127,8 @@ impl, LogT> Receipt for TypedEnvelope { self.data().logs_bloom() } - fn logs(&self) -> &[LogT] { - self.data().logs() + fn transaction_logs(&self) -> &[LogT] { + self.data().transaction_logs() } fn root_or_status(&self) -> RootOrStatus<'_> { diff --git a/crates/edr_optimism/src/receipt/execution.rs b/crates/edr_optimism/src/receipt/execution.rs index 054e7383e..ffd590efd 100644 --- a/crates/edr_optimism/src/receipt/execution.rs +++ b/crates/edr_optimism/src/receipt/execution.rs @@ -190,7 +190,7 @@ impl Receipt for Execution { } } - fn logs(&self) -> &[LogT] { + fn transaction_logs(&self) -> &[LogT] { match self { Execution::Legacy(receipt) => &receipt.logs, Execution::Eip658(receipt) => &receipt.logs, diff --git a/crates/edr_optimism/src/rpc.rs b/crates/edr_optimism/src/rpc.rs index 44f1f32b8..3e100dc6b 100644 --- a/crates/edr_optimism/src/rpc.rs +++ b/crates/edr_optimism/src/rpc.rs @@ -3,7 +3,7 @@ pub mod receipt; /// Types for Optimism RPC transaction. pub mod transaction; -use edr_eth::{env::SignedAuthorization, log::FilterLog, Address, Bloom, B256, U128, U256}; +use edr_eth::{env::SignedAuthorization, log::FilterLog, Address, Bloom, B256, U256}; use serde::{Deserialize, Serialize}; /// Transaction receipt @@ -98,12 +98,38 @@ pub struct BlockReceipt { pub struct Transaction { #[serde(flatten)] l1: edr_rpc_eth::Transaction, + /// ECDSA recovery id + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + )] + pub v: Option, + /// Y-parity for EIP-2930 and EIP-1559 transactions. In theory these + /// transactions types shouldn't have a `v` field, but in practice they + /// are returned by nodes. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + )] + pub y_parity: Option, + /// ECDSA signature r + #[serde(default, skip_serializing_if = "Option::is_none")] + pub r: Option, + /// ECDSA signature s + #[serde(default, skip_serializing_if = "Option::is_none")] + pub s: Option, /// Hash that uniquely identifies the source of the deposit. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub source_hash: Option, /// The ETH value to mint on L2 - #[serde(skip_serializing_if = "Option::is_none")] - pub mint: Option, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + )] + pub mint: Option, /// Field indicating whether the transaction is a system transaction, and /// therefore exempt from the L2 gas limit. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/edr_optimism/src/rpc/receipt.rs b/crates/edr_optimism/src/rpc/receipt.rs index 5ef1e69ac..c5e23af1a 100644 --- a/crates/edr_optimism/src/rpc/receipt.rs +++ b/crates/edr_optimism/src/rpc/receipt.rs @@ -5,52 +5,55 @@ use edr_eth::{ receipt::{Receipt as _, TransactionReceipt}, transaction::TransactionType as _, }; -use edr_rpc_eth::receipt::ToRpcReceipt; +use edr_rpc_eth::RpcTypeFrom; use revm::optimism::OptimismSpecId; use super::BlockReceipt; use crate::{eip2718::TypedEnvelope, receipt, transaction}; -impl ToRpcReceipt - for receipt::BlockReceipt>> +impl RpcTypeFrom>>> + for BlockReceipt { type Hardfork = OptimismSpecId; - fn to_rpc_receipt(&self, hardfork: Self::Hardfork) -> BlockReceipt { + fn rpc_type_from( + receipt: &receipt::BlockReceipt>>, + hardfork: Self::Hardfork, + ) -> Self { let transaction_type = if hardfork >= OptimismSpecId::BERLIN { - Some(u8::from(self.inner.transaction_type())) + Some(u8::from(receipt.inner.transaction_type())) } else { None }; - BlockReceipt { - block_hash: self.block_hash, - block_number: self.block_number, - transaction_hash: self.inner.transaction_hash, - transaction_index: self.inner.transaction_index, + Self { + block_hash: receipt.block_hash, + block_number: receipt.block_number, + transaction_hash: receipt.inner.transaction_hash, + transaction_index: receipt.inner.transaction_index, transaction_type, - from: self.inner.from, - to: self.inner.to, - cumulative_gas_used: self.inner.cumulative_gas_used(), - gas_used: self.inner.gas_used, - contract_address: self.inner.contract_address, - logs: self.inner.logs().to_vec(), - logs_bloom: *self.inner.logs_bloom(), - state_root: match self.inner.as_execution_receipt().data() { + from: receipt.inner.from, + to: receipt.inner.to, + cumulative_gas_used: receipt.inner.cumulative_gas_used(), + gas_used: receipt.inner.gas_used, + contract_address: receipt.inner.contract_address, + logs: receipt.inner.transaction_logs().to_vec(), + logs_bloom: *receipt.inner.logs_bloom(), + state_root: match receipt.inner.as_execution_receipt().data() { receipt::Execution::Legacy(receipt) => Some(receipt.root), receipt::Execution::Eip658(_) | receipt::Execution::Deposit(_) => None, }, - status: match self.inner.as_execution_receipt().data() { + status: match receipt.inner.as_execution_receipt().data() { receipt::Execution::Legacy(_) => None, receipt::Execution::Eip658(receipt) => Some(receipt.status), receipt::Execution::Deposit(receipt) => Some(receipt.status), }, - effective_gas_price: self.inner.effective_gas_price, - deposit_nonce: match self.inner.as_execution_receipt().data() { + effective_gas_price: receipt.inner.effective_gas_price, + deposit_nonce: match receipt.inner.as_execution_receipt().data() { receipt::Execution::Legacy(_) | receipt::Execution::Eip658(_) => None, receipt::Execution::Deposit(receipt) => Some(receipt.deposit_nonce), }, - deposit_receipt_version: match self.inner.as_execution_receipt().data() { + deposit_receipt_version: match receipt.inner.as_execution_receipt().data() { receipt::Execution::Legacy(_) | receipt::Execution::Eip658(_) => None, receipt::Execution::Deposit(receipt) => receipt.deposit_receipt_version, }, @@ -157,54 +160,57 @@ mod tests { use edr_rpc_eth::impl_execution_receipt_tests; use super::*; + use crate::OptimismChainSpec; impl_execution_receipt_tests! { - legacy => TypedEnvelope::Legacy(receipt::Execution::Legacy(receipt::execution::Legacy { - root: B256::random(), - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - })), - eip658_eip2930 => TypedEnvelope::Eip2930(receipt::Execution::Eip658(receipt::execution::Eip658 { - status: true, - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - })), - eip658_eip1559 => TypedEnvelope::Eip2930(receipt::Execution::Eip658(receipt::execution::Eip658 { - status: true, - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - })), - eip658_eip4844 => TypedEnvelope::Eip4844(receipt::Execution::Eip658(receipt::execution::Eip658 { - status: true, - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - })), - deposit => TypedEnvelope::Deposit(receipt::Execution::Deposit(receipt::execution::Deposit { - status: true, - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - deposit_nonce: 0x1234, - deposit_receipt_version: Some(1u8), - })), + OptimismChainSpec => { + legacy => TypedEnvelope::Legacy(receipt::Execution::Legacy(receipt::execution::Legacy { + root: B256::random(), + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + })), + eip658_eip2930 => TypedEnvelope::Eip2930(receipt::Execution::Eip658(receipt::execution::Eip658 { + status: true, + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + })), + eip658_eip1559 => TypedEnvelope::Eip2930(receipt::Execution::Eip658(receipt::execution::Eip658 { + status: true, + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + })), + eip658_eip4844 => TypedEnvelope::Eip4844(receipt::Execution::Eip658(receipt::execution::Eip658 { + status: true, + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + })), + deposit => TypedEnvelope::Deposit(receipt::Execution::Deposit(receipt::execution::Deposit { + status: true, + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + deposit_nonce: 0x1234, + deposit_receipt_version: Some(1u8), + })), + } } } diff --git a/crates/edr_optimism/src/rpc/transaction.rs b/crates/edr_optimism/src/rpc/transaction.rs index 7a223e5ce..33f9a9657 100644 --- a/crates/edr_optimism/src/rpc/transaction.rs +++ b/crates/edr_optimism/src/rpc/transaction.rs @@ -1,22 +1,21 @@ use std::sync::OnceLock; -use edr_eth::B256; -use edr_evm::transaction::{remote::EthRpcTransaction, TxKind}; -use edr_rpc_eth::TransactionConversionError as L1ConversionError; +use edr_eth::{signature::Signature, transaction::MaybeSignedTransaction, B256}; +use edr_evm::{ + block::transaction::{BlockDataForTransaction, TransactionAndBlock}, + transaction::{remote::EthRpcTransaction, TxKind}, +}; +use edr_rpc_eth::{ + RpcTypeFrom, TransactionConversionError as L1ConversionError, TransactionWithSignature, +}; +use revm::optimism::{OptimismSpecId, OptimismTransaction}; use super::Transaction; -use crate::transaction; - -impl Transaction { - /// Returns whether the transaction is a legacy transaction. - pub fn is_legacy(&self) -> bool { - matches!(self.l1.transaction_type, None | Some(0)) && matches!(self.l1.v, 27 | 28) - } -} +use crate::{transaction, OptimismChainSpec}; impl EthRpcTransaction for Transaction { fn block_hash(&self) -> Option<&B256> { - self.l1.block_hash() + self.l1.block_hash.as_ref() } } @@ -32,7 +31,7 @@ impl TryFrom for transaction::signed::Deposit { } else { TxKind::Create }, - mint: value.mint.map_or(0, |mint| mint.to()), + mint: value.mint.unwrap_or_default(), value: value.l1.value, gas_limit: value.l1.gas.to(), is_system_tx: value.is_system_tx.unwrap_or(false), @@ -54,6 +53,15 @@ pub enum ConversionError { /// Missing mint #[error("Missing mint")] Mint, + /// Missing signature R-value. + #[error("Missing signature R-value")] + SignatureR, + /// Missing signature S-value. + #[error("Missing signature S-value")] + SignatureS, + /// Missing signature V-value. + #[error("Missing signature V-value")] + SignatureV, /// Missing source hash #[error("Missing source hash")] SourceHash, @@ -79,19 +87,79 @@ impl TryFrom for transaction::Signed { }; let transaction = match transaction_type { - transaction::Type::Legacy => { - if value.is_legacy() { - Self::PreEip155Legacy(value.l1.into()) - } else { - Self::PostEip155Legacy(value.l1.into()) + transaction::Type::Deposit => Self::Deposit(value.try_into()?), + transaction_type => { + let r = value.r.ok_or(ConversionError::SignatureR)?; + let s = value.s.ok_or(ConversionError::SignatureS)?; + let v = value.v.ok_or(ConversionError::SignatureV)?; + + let transaction_with_signature = + TransactionWithSignature::new(value.l1, r, s, v, value.y_parity); + + match transaction_type { + transaction::Type::Legacy => { + if transaction_with_signature.is_legacy() { + Self::PreEip155Legacy(transaction_with_signature.into()) + } else { + Self::PostEip155Legacy(transaction_with_signature.into()) + } + } + transaction::Type::Eip2930 => { + Self::Eip2930(transaction_with_signature.try_into()?) + } + transaction::Type::Eip1559 => { + Self::Eip1559(transaction_with_signature.try_into()?) + } + transaction::Type::Eip4844 => { + Self::Eip4844(transaction_with_signature.try_into()?) + } + transaction::Type::Deposit => unreachable!("already handled"), } } - transaction::Type::Eip2930 => Self::Eip2930(value.l1.try_into()?), - transaction::Type::Eip1559 => Self::Eip1559(value.l1.try_into()?), - transaction::Type::Eip4844 => Self::Eip4844(value.l1.try_into()?), - transaction::Type::Deposit => Self::Deposit(value.try_into()?), }; Ok(transaction) } } + +impl RpcTypeFrom> for Transaction { + type Hardfork = OptimismSpecId; + + fn rpc_type_from( + value: &TransactionAndBlock, + hardfork: Self::Hardfork, + ) -> Self { + let (header, transaction_index) = value + .block_data + .as_ref() + .map( + |BlockDataForTransaction { + block, + transaction_index, + }| (block.header(), *transaction_index), + ) + .unzip(); + + let l1 = edr_rpc_eth::Transaction::new( + &value.transaction, + header, + transaction_index, + value.is_pending, + hardfork.into(), + ); + + let signature = value.transaction.maybe_signature(); + + Self { + l1, + v: signature.map(Signature::v), + // Following Hardhat in always returning `v` instead of `y_parity`. + y_parity: None, + r: signature.map(Signature::r), + s: signature.map(Signature::s), + source_hash: value.transaction.source_hash().copied(), + mint: value.transaction.mint().copied(), + is_system_tx: value.transaction.is_system_transaction(), + } + } +} diff --git a/crates/edr_optimism/src/spec.rs b/crates/edr_optimism/src/spec.rs index 5dbd71a6d..9460c48ec 100644 --- a/crates/edr_optimism/src/spec.rs +++ b/crates/edr_optimism/src/spec.rs @@ -1,19 +1,21 @@ use alloy_rlp::RlpEncodable; use edr_eth::{ - block::{BlobGas, PartialHeader}, + block::{self, BlobGas, PartialHeader}, chain_spec::EthHeaderConstants, eips::eip1559::{BaseFeeParams, ConstantBaseFeeParams, ForkBaseFeeParams}, env::{BlobExcessGasAndPrice, BlockEnv}, + result::InvalidTransaction, U256, }; use edr_evm::{ chain_spec::{BlockEnvConstructor, ChainSpec}, + transaction::{TransactionError, TransactionValidation}, RemoteBlockConversionError, }; use edr_rpc_eth::spec::RpcSpec; use revm::{ handler::register::HandleRegisters, - optimism::{OptimismHaltReason, OptimismSpecId}, + optimism::{OptimismHaltReason, OptimismInvalidTransaction, OptimismSpecId}, EvmHandler, }; use serde::{de::DeserializeOwned, Serialize}; @@ -27,8 +29,10 @@ pub struct OptimismChainSpec; impl RpcSpec for OptimismChainSpec { type ExecutionReceipt = TypedEnvelope>; type RpcBlock = edr_rpc_eth::Block where Data: Default + DeserializeOwned + Serialize; + type RpcCallRequest = edr_rpc_eth::CallRequest; type RpcReceipt = rpc::BlockReceipt; type RpcTransaction = rpc::Transaction; + type RpcTransactionRequest = edr_rpc_eth::TransactionRequest; } impl revm::primitives::EvmWiring for OptimismChainSpec { @@ -55,7 +59,7 @@ impl revm::EvmWiring for OptimismChainSpec { } } -impl BlockEnvConstructor for revm::primitives::BlockEnv { +impl BlockEnvConstructor for revm::primitives::BlockEnv { fn new_block_env(header: &PartialHeader, hardfork: OptimismSpecId) -> Self { BlockEnv { number: U256::from(header.number), @@ -77,12 +81,46 @@ impl BlockEnvConstructor for revm::primitives::BlockEnv { } } +impl BlockEnvConstructor for revm::primitives::BlockEnv { + fn new_block_env(header: &block::Header, hardfork: OptimismSpecId) -> Self { + BlockEnv { + number: U256::from(header.number), + coinbase: header.beneficiary, + timestamp: U256::from(header.timestamp), + difficulty: header.difficulty, + basefee: header.base_fee_per_gas.unwrap_or(U256::ZERO), + gas_limit: U256::from(header.gas_limit), + prevrandao: if hardfork >= OptimismSpecId::MERGE { + Some(header.mix_hash) + } else { + None + }, + blob_excess_gas_and_price: header + .blob_gas + .as_ref() + .map(|BlobGas { excess_gas, .. }| BlobExcessGasAndPrice::new(*excess_gas)), + } + } +} + impl ChainSpec for OptimismChainSpec { type ReceiptBuilder = receipt::execution::Builder; type RpcBlockConversionError = RemoteBlockConversionError; type RpcReceiptConversionError = rpc::receipt::ConversionError; type RpcTransactionConversionError = rpc::transaction::ConversionError; + fn cast_transaction_error( + error: ::ValidationError, + ) -> TransactionError { + match error { + OptimismInvalidTransaction::Base(InvalidTransaction::LackOfFundForMaxFee { + fee, + balance, + }) => TransactionError::LackOfFundForMaxFee { fee, balance }, + remainder => TransactionError::InvalidTransaction(remainder), + } + } + fn chain_hardfork_activations( chain_id: u64, ) -> Option<&'static edr_evm::hardfork::Activations> { diff --git a/crates/edr_optimism/src/transaction/signed.rs b/crates/edr_optimism/src/transaction/signed.rs index 2e233cf7e..363f0d65a 100644 --- a/crates/edr_optimism/src/transaction/signed.rs +++ b/crates/edr_optimism/src/transaction/signed.rs @@ -7,10 +7,11 @@ use std::sync::OnceLock; use alloy_rlp::{Buf, RlpDecodable, RlpEncodable}; pub use edr_eth::transaction::signed::{Eip155, Eip1559, Eip2930, Eip4844, Legacy}; use edr_eth::{ - signature::Fakeable, + signature::{Fakeable, Signature}, transaction::{ - SignedTransaction, Transaction, TransactionMut, TransactionType, TransactionValidation, - TxKind, INVALID_TX_TYPE_ERROR_MESSAGE, + ExecutableTransaction, HasAccessList, IsEip4844, IsLegacy, MaybeSignedTransaction, + Transaction, TransactionMut, TransactionType, TransactionValidation, TxKind, + INVALID_TX_TYPE_ERROR_MESSAGE, }, Address, Bytes, B256, U256, }; @@ -113,6 +114,63 @@ impl Default for Signed { } } +impl ExecutableTransaction for Signed { + fn effective_gas_price(&self, block_base_fee: U256) -> Option { + match self { + Signed::PreEip155Legacy(tx) => tx.effective_gas_price(block_base_fee), + Signed::PostEip155Legacy(tx) => tx.effective_gas_price(block_base_fee), + Signed::Eip2930(tx) => tx.effective_gas_price(block_base_fee), + Signed::Eip1559(tx) => tx.effective_gas_price(block_base_fee), + Signed::Eip4844(tx) => tx.effective_gas_price(block_base_fee), + Signed::Deposit(tx) => tx.effective_gas_price(block_base_fee), + } + } + + fn max_fee_per_gas(&self) -> Option<&U256> { + match self { + Signed::PreEip155Legacy(tx) => tx.max_fee_per_gas(), + Signed::PostEip155Legacy(tx) => tx.max_fee_per_gas(), + Signed::Eip2930(tx) => tx.max_fee_per_gas(), + Signed::Eip1559(tx) => tx.max_fee_per_gas(), + Signed::Eip4844(tx) => tx.max_fee_per_gas(), + Signed::Deposit(tx) => tx.max_fee_per_gas(), + } + } + + fn rlp_encoding(&self) -> &Bytes { + match self { + Signed::PreEip155Legacy(tx) => tx.rlp_encoding(), + Signed::PostEip155Legacy(tx) => tx.rlp_encoding(), + Signed::Eip2930(tx) => tx.rlp_encoding(), + Signed::Eip1559(tx) => tx.rlp_encoding(), + Signed::Eip4844(tx) => tx.rlp_encoding(), + Signed::Deposit(tx) => tx.rlp_encoding(), + } + } + + fn total_blob_gas(&self) -> Option { + match self { + Signed::PreEip155Legacy(tx) => tx.total_blob_gas(), + Signed::PostEip155Legacy(tx) => tx.total_blob_gas(), + Signed::Eip2930(tx) => tx.total_blob_gas(), + Signed::Eip1559(tx) => tx.total_blob_gas(), + Signed::Eip4844(tx) => tx.total_blob_gas(), + Signed::Deposit(tx) => tx.total_blob_gas(), + } + } + + fn transaction_hash(&self) -> &B256 { + match self { + Signed::PreEip155Legacy(tx) => tx.transaction_hash(), + Signed::PostEip155Legacy(tx) => tx.transaction_hash(), + Signed::Eip2930(tx) => tx.transaction_hash(), + Signed::Eip1559(tx) => tx.transaction_hash(), + Signed::Eip4844(tx) => tx.transaction_hash(), + Signed::Deposit(tx) => tx.transaction_hash(), + } + } +} + impl From for Signed { fn from(value: edr_eth::transaction::Signed) -> Self { match value { @@ -125,90 +183,68 @@ impl From for Signed { } } -impl OptimismTransaction for Signed { - fn source_hash(&self) -> Option<&B256> { +impl HasAccessList for Signed { + fn has_access_list(&self) -> bool { match self { - Signed::Deposit(tx) => Some(&tx.source_hash), - _ => None, - } - } - - fn mint(&self) -> Option<&u128> { - match self { - Signed::Deposit(tx) => Some(&tx.mint), - _ => None, + Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) | Signed::Deposit(_) => false, + Signed::Eip2930(_) | Signed::Eip1559(_) | Signed::Eip4844(_) => true, } } +} - fn is_system_transaction(&self) -> Option { - match self { - Signed::Deposit(tx) => Some(tx.is_system_tx), - _ => None, - } +impl IsEip4844 for Signed { + fn is_eip4844(&self) -> bool { + matches!(self, Signed::Eip4844(_)) } +} - fn enveloped_tx(&self) -> Option { - let enveloped = alloy_rlp::encode(self); - Some(enveloped.into()) +impl IsLegacy for Signed { + fn is_legacy(&self) -> bool { + matches!( + self, + Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) + ) } } -impl SignedTransaction for Signed { - fn effective_gas_price(&self, block_base_fee: U256) -> Option { +impl MaybeSignedTransaction for Signed { + fn maybe_signature(&self) -> Option<&dyn Signature> { match self { - Signed::PreEip155Legacy(_) - | Signed::PostEip155Legacy(_) - | Signed::Eip2930(_) - | Signed::Deposit(_) => None, - Signed::Eip1559(tx) => Some( - tx.max_fee_per_gas - .min(block_base_fee + tx.max_priority_fee_per_gas), - ), - Signed::Eip4844(tx) => Some( - tx.max_fee_per_gas - .min(block_base_fee + tx.max_priority_fee_per_gas), - ), + Signed::PreEip155Legacy(tx) => Some(&tx.signature), + Signed::PostEip155Legacy(tx) => Some(&tx.signature), + Signed::Eip2930(tx) => Some(&tx.signature), + Signed::Eip1559(tx) => Some(&tx.signature), + Signed::Eip4844(tx) => Some(&tx.signature), + Signed::Deposit(_) => None, } } +} - fn max_fee_per_gas(&self) -> Option { +impl OptimismTransaction for Signed { + fn source_hash(&self) -> Option<&B256> { match self { - Signed::PreEip155Legacy(_) - | Signed::PostEip155Legacy(_) - | Signed::Eip2930(_) - | Signed::Deposit(_) => None, - Signed::Eip1559(tx) => Some(tx.max_fee_per_gas), - Signed::Eip4844(tx) => Some(tx.max_fee_per_gas), + Signed::Deposit(tx) => Some(&tx.source_hash), + _ => None, } } - fn rlp_encoding(&self) -> &Bytes { + fn mint(&self) -> Option<&u128> { match self { - Signed::PreEip155Legacy(tx) => tx.rlp_encoding(), - Signed::PostEip155Legacy(tx) => tx.rlp_encoding(), - Signed::Eip2930(tx) => tx.rlp_encoding(), - Signed::Eip1559(tx) => tx.rlp_encoding(), - Signed::Eip4844(tx) => tx.rlp_encoding(), - Signed::Deposit(tx) => tx.rlp_encoding(), + Signed::Deposit(tx) => Some(&tx.mint), + _ => None, } } - fn total_blob_gas(&self) -> Option { + fn is_system_transaction(&self) -> Option { match self { - Signed::Eip4844(tx) => Some(tx.total_blob_gas()), + Signed::Deposit(tx) => Some(tx.is_system_tx), _ => None, } } - fn transaction_hash(&self) -> &B256 { - match self { - Signed::PreEip155Legacy(tx) => tx.transaction_hash(), - Signed::PostEip155Legacy(tx) => tx.transaction_hash(), - Signed::Eip2930(tx) => tx.transaction_hash(), - Signed::Eip1559(tx) => tx.transaction_hash(), - Signed::Eip4844(tx) => tx.transaction_hash(), - Signed::Deposit(tx) => tx.transaction_hash(), - } + fn enveloped_tx(&self) -> Option { + let enveloped = alloy_rlp::encode(self); + Some(enveloped.into()) } } @@ -220,135 +256,140 @@ impl Transaction for Signed { Signed::Eip2930(tx) => tx.caller(), Signed::Eip1559(tx) => tx.caller(), Signed::Eip4844(tx) => tx.caller(), - Signed::Deposit(tx) => &tx.from, + Signed::Deposit(tx) => tx.caller(), } } fn gas_limit(&self) -> u64 { match self { - Signed::PreEip155Legacy(tx) => tx.gas_limit, - Signed::PostEip155Legacy(tx) => tx.gas_limit, - Signed::Eip2930(tx) => tx.gas_limit, - Signed::Eip1559(tx) => tx.gas_limit, - Signed::Eip4844(tx) => tx.gas_limit, - Signed::Deposit(tx) => tx.gas_limit, + Signed::PreEip155Legacy(tx) => tx.gas_limit(), + Signed::PostEip155Legacy(tx) => tx.gas_limit(), + Signed::Eip2930(tx) => tx.gas_limit(), + Signed::Eip1559(tx) => tx.gas_limit(), + Signed::Eip4844(tx) => tx.gas_limit(), + Signed::Deposit(tx) => tx.gas_limit(), } } fn gas_price(&self) -> &U256 { match self { - Signed::PreEip155Legacy(tx) => &tx.gas_price, - Signed::PostEip155Legacy(tx) => &tx.gas_price, - Signed::Eip2930(tx) => &tx.gas_price, - Signed::Eip1559(tx) => &tx.max_fee_per_gas, - Signed::Eip4844(tx) => &tx.max_fee_per_gas, - // No gas is refunded as ETH. (either by not refunding or utilizing the fact the - // gas-price of the deposit is 0) - Signed::Deposit(_) => &U256::ZERO, + Signed::PreEip155Legacy(tx) => tx.gas_price(), + Signed::PostEip155Legacy(tx) => tx.gas_price(), + Signed::Eip2930(tx) => tx.gas_price(), + Signed::Eip1559(tx) => tx.gas_price(), + Signed::Eip4844(tx) => tx.gas_price(), + Signed::Deposit(tx) => tx.gas_price(), } } fn kind(&self) -> TxKind { match self { - Signed::PreEip155Legacy(tx) => tx.kind, - Signed::PostEip155Legacy(tx) => tx.kind, - Signed::Eip2930(tx) => tx.kind, - Signed::Eip1559(tx) => tx.kind, - Signed::Eip4844(tx) => TxKind::Call(tx.to), - Signed::Deposit(tx) => tx.to, + Signed::PreEip155Legacy(tx) => tx.kind(), + Signed::PostEip155Legacy(tx) => tx.kind(), + Signed::Eip2930(tx) => tx.kind(), + Signed::Eip1559(tx) => tx.kind(), + Signed::Eip4844(tx) => tx.kind(), + Signed::Deposit(tx) => tx.kind(), } } fn value(&self) -> &U256 { match self { - Signed::PreEip155Legacy(tx) => &tx.value, - Signed::PostEip155Legacy(tx) => &tx.value, - Signed::Eip2930(tx) => &tx.value, - Signed::Eip1559(tx) => &tx.value, - Signed::Eip4844(tx) => &tx.value, - Signed::Deposit(tx) => &tx.value, + Signed::PreEip155Legacy(tx) => tx.value(), + Signed::PostEip155Legacy(tx) => tx.value(), + Signed::Eip2930(tx) => tx.value(), + Signed::Eip1559(tx) => tx.value(), + Signed::Eip4844(tx) => tx.value(), + Signed::Deposit(tx) => tx.value(), } } fn data(&self) -> &Bytes { match self { - Signed::PreEip155Legacy(tx) => &tx.input, - Signed::PostEip155Legacy(tx) => &tx.input, - Signed::Eip2930(tx) => &tx.input, - Signed::Eip1559(tx) => &tx.input, - Signed::Eip4844(tx) => &tx.input, - Signed::Deposit(tx) => &tx.data, + Signed::PreEip155Legacy(tx) => tx.data(), + Signed::PostEip155Legacy(tx) => tx.data(), + Signed::Eip2930(tx) => tx.data(), + Signed::Eip1559(tx) => tx.data(), + Signed::Eip4844(tx) => tx.data(), + Signed::Deposit(tx) => tx.data(), } } fn nonce(&self) -> u64 { match self { - Signed::PreEip155Legacy(tx) => tx.nonce, - Signed::PostEip155Legacy(tx) => tx.nonce, - Signed::Eip2930(tx) => tx.nonce, - Signed::Eip1559(tx) => tx.nonce, - Signed::Eip4844(tx) => tx.nonce, - // Before Regolith: the nonce is always 0 - // With Regolith: the nonce is set to the depositNonce attribute of the corresponding - // transaction receipt. - Signed::Deposit(_) => 0, + Signed::PreEip155Legacy(tx) => tx.nonce(), + Signed::PostEip155Legacy(tx) => tx.nonce(), + Signed::Eip2930(tx) => tx.nonce(), + Signed::Eip1559(tx) => tx.nonce(), + Signed::Eip4844(tx) => tx.nonce(), + Signed::Deposit(tx) => tx.nonce(), } } fn chain_id(&self) -> Option { match self { - Signed::PreEip155Legacy(_) | Signed::Deposit(_) => None, - Signed::PostEip155Legacy(tx) => Some(tx.chain_id()), - Signed::Eip2930(tx) => Some(tx.chain_id), - Signed::Eip1559(tx) => Some(tx.chain_id), - Signed::Eip4844(tx) => Some(tx.chain_id), + Signed::PreEip155Legacy(tx) => tx.chain_id(), + Signed::PostEip155Legacy(tx) => tx.chain_id(), + Signed::Eip2930(tx) => tx.chain_id(), + Signed::Eip1559(tx) => tx.chain_id(), + Signed::Eip4844(tx) => tx.chain_id(), + Signed::Deposit(tx) => tx.chain_id(), } } fn access_list(&self) -> &[edr_eth::AccessListItem] { match self { - Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) | Signed::Deposit(_) => &[], - Signed::Eip2930(tx) => &tx.access_list.0, - Signed::Eip1559(tx) => &tx.access_list.0, - Signed::Eip4844(tx) => &tx.access_list.0, + Signed::PreEip155Legacy(tx) => tx.access_list(), + Signed::PostEip155Legacy(tx) => tx.access_list(), + Signed::Eip2930(tx) => tx.access_list(), + Signed::Eip1559(tx) => tx.access_list(), + Signed::Eip4844(tx) => tx.access_list(), + Signed::Deposit(tx) => tx.access_list(), } } fn max_priority_fee_per_gas(&self) -> Option<&U256> { match self { - Signed::PreEip155Legacy(_) | Signed::PostEip155Legacy(_) | Signed::Eip2930(_) => None, - Signed::Eip1559(tx) => Some(&tx.max_priority_fee_per_gas), - Signed::Eip4844(tx) => Some(&tx.max_priority_fee_per_gas), - // No transaction priority fee is charged. No payment is made to the block - // fee-recipient. - Signed::Deposit(_) => Some(&U256::ZERO), + Signed::PreEip155Legacy(tx) => tx.max_priority_fee_per_gas(), + Signed::PostEip155Legacy(tx) => tx.max_priority_fee_per_gas(), + Signed::Eip2930(tx) => tx.max_priority_fee_per_gas(), + Signed::Eip1559(tx) => tx.max_priority_fee_per_gas(), + Signed::Eip4844(tx) => tx.max_priority_fee_per_gas(), + Signed::Deposit(tx) => tx.max_priority_fee_per_gas(), } } fn blob_hashes(&self) -> &[B256] { match self { - Signed::PreEip155Legacy(_) - | Signed::PostEip155Legacy(_) - | Signed::Eip2930(_) - | Signed::Eip1559(_) - | Signed::Deposit(_) => &[], - Signed::Eip4844(tx) => &tx.blob_hashes, + Signed::PreEip155Legacy(tx) => tx.blob_hashes(), + Signed::PostEip155Legacy(tx) => tx.blob_hashes(), + Signed::Eip2930(tx) => tx.blob_hashes(), + Signed::Eip1559(tx) => tx.blob_hashes(), + Signed::Eip4844(tx) => tx.blob_hashes(), + Signed::Deposit(tx) => tx.blob_hashes(), } } fn max_fee_per_blob_gas(&self) -> Option<&U256> { match self { - Signed::PreEip155Legacy(_) - | Signed::PostEip155Legacy(_) - | Signed::Eip2930(_) - | Signed::Eip1559(_) - | Signed::Deposit(_) => None, - Signed::Eip4844(tx) => Some(&tx.max_fee_per_blob_gas), + Signed::PreEip155Legacy(tx) => tx.max_fee_per_blob_gas(), + Signed::PostEip155Legacy(tx) => tx.max_fee_per_blob_gas(), + Signed::Eip2930(tx) => tx.max_fee_per_blob_gas(), + Signed::Eip1559(tx) => tx.max_fee_per_blob_gas(), + Signed::Eip4844(tx) => tx.max_fee_per_blob_gas(), + Signed::Deposit(tx) => tx.max_fee_per_blob_gas(), } } fn authorization_list(&self) -> Option<&edr_eth::env::AuthorizationList> { - None + match self { + Signed::PreEip155Legacy(tx) => tx.authorization_list(), + Signed::PostEip155Legacy(tx) => tx.authorization_list(), + Signed::Eip2930(tx) => tx.authorization_list(), + Signed::Eip1559(tx) => tx.authorization_list(), + Signed::Eip4844(tx) => tx.authorization_list(), + Signed::Deposit(tx) => tx.authorization_list(), + } } } diff --git a/crates/edr_optimism/src/transaction/signed/deposit.rs b/crates/edr_optimism/src/transaction/signed/deposit.rs index 09e3d4025..afe1538a6 100644 --- a/crates/edr_optimism/src/transaction/signed/deposit.rs +++ b/crates/edr_optimism/src/transaction/signed/deposit.rs @@ -1,5 +1,10 @@ use alloy_rlp::Encodable; -use edr_eth::{utils::enveloped, Bytes, B256}; +use edr_eth::{ + env::AuthorizationList, + transaction::{ExecutableTransaction, Transaction, TxKind}, + utils::enveloped, + AccessListItem, Address, Bytes, B256, U256, +}; use revm::primitives::keccak256; use super::Deposit; @@ -7,9 +12,18 @@ use super::Deposit; impl Deposit { /// The type identifier for a deposit transaction. pub const TYPE: u8 = 0x7E; +} + +impl ExecutableTransaction for Deposit { + fn effective_gas_price(&self, _block_base_fee: U256) -> Option { + None + } + + fn max_fee_per_gas(&self) -> Option<&U256> { + None + } - /// Returns the (cached) RLP-encoding of the transaction. - pub fn rlp_encoding(&self) -> &Bytes { + fn rlp_encoding(&self) -> &Bytes { self.rlp_encoding.get_or_init(|| { let mut encoded = Vec::with_capacity(1 + self.length()); enveloped(Self::TYPE, self, &mut encoded); @@ -17,8 +31,11 @@ impl Deposit { }) } - /// Returns the (cached) hash of the transaction. - pub fn transaction_hash(&self) -> &B256 { + fn total_blob_gas(&self) -> Option { + None + } + + fn transaction_hash(&self) -> &B256 { self.hash.get_or_init(|| keccak256(self.rlp_encoding())) } } @@ -37,13 +54,74 @@ impl PartialEq for Deposit { } } +impl Transaction for Deposit { + fn caller(&self) -> &Address { + &self.from + } + + fn gas_limit(&self) -> u64 { + self.gas_limit + } + + fn gas_price(&self) -> &U256 { + // No gas is refunded as ETH. (either by not refunding or utilizing the fact the + // gas-price of the deposit is 0) + &U256::ZERO + } + + fn kind(&self) -> TxKind { + self.to + } + + fn value(&self) -> &U256 { + &self.value + } + + fn data(&self) -> &Bytes { + &self.data + } + + fn nonce(&self) -> u64 { + // Before Regolith: the nonce is always 0 + // With Regolith: the nonce is set to the depositNonce attribute of the + // corresponding transaction receipt. + 0 + } + + fn chain_id(&self) -> Option { + None + } + + fn access_list(&self) -> &[AccessListItem] { + &[] + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + // No transaction priority fee is charged. No payment is made to the block + // fee-recipient. + Some(&U256::ZERO) + } + + fn blob_hashes(&self) -> &[B256] { + &[] + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + None + } + + fn authorization_list(&self) -> Option<&AuthorizationList> { + None + } +} + #[cfg(test)] mod tests { use std::{str::FromStr as _, sync::OnceLock}; use edr_eth::{ address, - transaction::{SignedTransaction as _, TxKind}, + transaction::{ExecutableTransaction as _, TxKind}, Bytes, U256, }; use revm::primitives::b256; diff --git a/crates/edr_optimism/src/transaction/type.rs b/crates/edr_optimism/src/transaction/type.rs index 1dc280b21..a557af50b 100644 --- a/crates/edr_optimism/src/transaction/type.rs +++ b/crates/edr_optimism/src/transaction/type.rs @@ -1,6 +1,9 @@ use std::str::FromStr; -use edr_eth::{transaction::ParseError, U8}; +use edr_eth::{ + transaction::{IsEip4844, ParseError}, + U8, +}; use super::{signed, Type}; @@ -29,6 +32,12 @@ impl FromStr for Type { } } +impl IsEip4844 for Type { + fn is_eip4844(&self) -> bool { + *self == Type::Eip4844 + } +} + impl TryFrom for Type { type Error = u8; diff --git a/crates/edr_provider/src/config.rs b/crates/edr_provider/src/config.rs index 3f376e3b8..29f173a26 100644 --- a/crates/edr_provider/src/config.rs +++ b/crates/edr_provider/src/config.rs @@ -1,7 +1,7 @@ use std::{num::NonZeroU64, path::PathBuf, time::SystemTime}; use derive_where::derive_where; -use edr_eth::{block::BlobGas, AccountInfo, Address, ChainId, HashMap, SpecId, B256, U256}; +use edr_eth::{block::BlobGas, AccountInfo, Address, ChainId, HashMap, B256, U256}; use edr_evm::{chain_spec::ChainSpec, hardfork, MineOrdering}; use rand::Rng; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -93,7 +93,7 @@ pub struct ProviderConfig { pub fork: Option, // Genesis accounts in addition to accounts. Useful for adding impersonated accounts for tests. pub genesis_accounts: HashMap, - pub hardfork: SpecId, + pub hardfork: ChainSpecT::Hardfork, pub initial_base_fee_per_gas: Option, pub initial_blob_gas: Option, pub initial_date: Option, diff --git a/crates/edr_provider/src/console_log.rs b/crates/edr_provider/src/console_log.rs index c5e524a34..ad1b08caa 100644 --- a/crates/edr_provider/src/console_log.rs +++ b/crates/edr_provider/src/console_log.rs @@ -49,9 +49,9 @@ impl ConsoleLogCollector { #[cfg(test)] pub(crate) mod tests { - use anyhow::Context; use edr_eth::{ + chain_spec::L1ChainSpec, hex, transaction::{self, request::TransactionRequestAndSender, TxKind}, Bytes, U256, @@ -60,12 +60,12 @@ pub(crate) mod tests { use crate::{data::ProviderData, time::TimeSinceEpoch}; pub struct ConsoleLogTransaction { - pub transaction: TransactionRequestAndSender, + pub transaction: TransactionRequestAndSender, pub expected_call_data: Bytes, } pub fn deploy_console_log_contract( - provider_data: &mut ProviderData, + provider_data: &mut ProviderData, ) -> anyhow::Result { // Compiled with solc 0.8.17, without optimizations /* diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index 6d51f7fc4..ec7a470b0 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -16,32 +16,34 @@ use alloy_dyn_abi::eip712::TypedData; use edr_eth::{ block::{ calculate_next_base_fee_per_blob_gas, calculate_next_base_fee_per_gas, miner_reward, - BlobGas, BlockOptions, + BlockOptions, }, - chain_spec::L1ChainSpec, db::StateRef, - env::{BlobExcessGasAndPrice, BlockEnv, CfgEnv}, + env::CfgEnv, fee_history::FeeHistoryResult, filter::{FilteredEvents, LogOutput, SubscriptionType}, log::FilterLog, receipt::Receipt as _, - result::ExecutionResult, + result::{ExecutionResult, InvalidTransaction}, reward_percentile::RewardPercentile, signature::{self, RecoveryMessage}, state::{Account, EvmStorageSlot}, transaction::{ - request::TransactionRequestAndSender, SignedTransaction as _, Transaction as _, - TransactionType as _, + request::TransactionRequestAndSender, + signed::{FakeSign as _, Sign as _}, + ExecutableTransaction as _, IsEip4844, Transaction as _, TransactionMut, TransactionType, + TransactionValidation, }, AccountInfo, Address, BlockSpec, BlockTag, Bytecode, Bytes, Eip1898BlockSpec, HashMap, HashSet, Precompile, SpecId, B256, KECCAK_EMPTY, U256, }; use edr_evm::{ + block::transaction::{BlockDataForTransaction, TransactionAndBlock}, blockchain::{ Blockchain, BlockchainError, ForkedBlockchain, ForkedCreationError, GenesisBlockOptions, LocalBlockchain, LocalCreationError, SyncBlockchain, }, - chain_spec::ChainSpec, + chain_spec::{BlockEnvConstructor as _, ChainSpec, SyncChainSpec}, debug_trace_transaction, evm::handler::CfgEnvWithEvmWiring, execution_result_to_debug_result, mempool, mine_block, mine_block_with_single_transaction, @@ -82,6 +84,7 @@ use crate::{ pending::BlockchainWithPending, requests::hardhat::rpc_types::{ForkConfig, ForkMetadata}, snapshot::Snapshot, + spec::{ProviderSpec, SyncProviderSpec}, time::{CurrentTime, TimeSinceEpoch}, MiningConfig, ProviderConfig, ProviderError, SubscriptionEvent, SubscriptionEventData, SyncSubscriberCallback, @@ -93,28 +96,28 @@ const DEFAULT_MAX_CACHED_STATES: usize = 100_000; /// The result of executing an `eth_call`. #[derive(Clone, Debug)] -pub struct CallResult { +pub struct CallResult { pub console_log_inputs: Vec, - pub execution_result: ExecutionResult, - pub trace: Trace, + pub execution_result: ExecutionResult, + pub trace: Trace, } #[derive(Clone)] -pub struct EstimateGasResult { +pub struct EstimateGasResult { pub estimation: u64, - pub traces: Vec>, + pub traces: Vec>, } -pub struct SendTransactionResult { +pub struct SendTransactionResult { pub transaction_hash: B256, - pub mining_results: Vec>>, + pub mining_results: Vec>>, } -impl SendTransactionResult { +impl SendTransactionResult { /// Present if the transaction was auto-mined. pub fn transaction_result_and_trace( &self, - ) -> Option<(&ExecutionResult, &Trace)> { + ) -> Option<(&ExecutionResult, &Trace)> { self.mining_results.iter().find_map(|result| { izip!( result.block.transactions().iter(), @@ -132,8 +135,10 @@ impl SendTransactionResult { } } -impl From for (B256, Vec>) { - fn from(value: SendTransactionResult) -> Self { +impl From> + for (B256, Vec>) +{ + fn from(value: SendTransactionResult) -> Self { let SendTransactionResult { transaction_hash, mining_results, @@ -155,7 +160,7 @@ where { /// A blockchain error #[error(transparent)] - Blockchain(BlockchainError), + Blockchain(BlockchainError), /// An error that occurred while constructing a forked blockchain. #[error(transparent)] ForkedBlockchainCreation(#[from] ForkedCreationError), @@ -174,12 +179,15 @@ where RpcClient(#[from] RpcClientError), } -pub struct ProviderData { +pub struct ProviderData< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch = CurrentTime, +> { runtime_handle: runtime::Handle, - initial_config: ProviderConfig, - blockchain: Box, StateError>>, + initial_config: ProviderConfig, + blockchain: Box, StateError>>, pub irregular_state: IrregularState, - mem_pool: MemPool, + mem_pool: MemPool, beneficiary: Address, custom_precompiles: HashMap, min_gas_price: U256, @@ -189,13 +197,13 @@ pub struct ProviderData { fork_metadata: Option, // Must be set if the provider is created with a fork config. // Hack to get around the type erasure with the dyn blockchain trait. - rpc_client: Option>>, + rpc_client: Option>>, instance_id: B256, is_auto_mining: bool, next_block_base_fee_per_gas: Option, next_block_timestamp: Option, next_snapshot_id: u64, - snapshots: BTreeMap>, + snapshots: BTreeMap>, allow_blocks_with_same_timestamp: bool, allow_unlimited_contract_size: bool, verbose_tracing: bool, @@ -203,9 +211,9 @@ pub struct ProviderData { local_accounts: IndexMap, filters: HashMap, last_filter_id: U256, - logger: Box>>, + logger: Box>>, impersonated_accounts: HashSet
, - subscriber_callback: Box, + subscriber_callback: Box>, timer: TimerT, call_override: Option>, // We need the Arc to let us avoid returning references to the cache entries which need &mut @@ -215,172 +223,37 @@ pub struct ProviderData { block_number_to_state_id: HashTrieMapSync, } -impl ProviderData { - pub fn new( - runtime_handle: runtime::Handle, - logger: Box>>, - subscriber_callback: Box, - call_override: Option>, - config: ProviderConfig, - timer: TimerT, - ) -> Result> { - let InitialAccounts { - local_accounts, - genesis_accounts, - } = create_accounts(&config); - - let BlockchainAndState { - blockchain, - fork_metadata, - rpc_client, - state, - irregular_state, - prev_randao_generator, - block_time_offset_seconds, - next_block_base_fee_per_gas, - } = create_blockchain_and_state(runtime_handle.clone(), &config, &timer, genesis_accounts)?; - - let max_cached_states = std::env::var(EDR_MAX_CACHED_STATES_ENV_VAR).map_or_else( - |err| match err { - std::env::VarError::NotPresent => { - Ok(NonZeroUsize::new(DEFAULT_MAX_CACHED_STATES).expect("constant is non-zero")) - } - std::env::VarError::NotUnicode(s) => Err(CreationError::InvalidMaxCachedStates(s)), - }, - |s| { - s.parse() - .map_err(|_err| CreationError::InvalidMaxCachedStates(s.into())) - }, - )?; - let mut block_state_cache = LruCache::new(max_cached_states); - let mut block_number_to_state_id = HashTrieMapSync::default(); - - let current_state_id = StateId::default(); - block_state_cache.push(current_state_id, Arc::new(state)); - block_number_to_state_id.insert_mut(blockchain.last_block_number(), current_state_id); - - let allow_blocks_with_same_timestamp = config.allow_blocks_with_same_timestamp; - let allow_unlimited_contract_size = config.allow_unlimited_contract_size; - let beneficiary = config.coinbase; - let block_gas_limit = config.block_gas_limit; - let is_auto_mining = config.mining.auto_mine; - let min_gas_price = config.min_gas_price; - - let parent_beacon_block_root_generator = if let Some(initial_parent_beacon_block_root) = - &config.initial_parent_beacon_block_root - { - RandomHashGenerator::with_value(*initial_parent_beacon_block_root) - } else { - RandomHashGenerator::with_seed("randomParentBeaconBlockRootSeed") - }; - - let custom_precompiles = { - let mut precompiles = HashMap::new(); - - if config.enable_rip_7212 { - // EIP-7212: secp256r1 P256verify - precompiles.insert(secp256r1::P256VERIFY.0, secp256r1::P256VERIFY.1); - } - - precompiles - }; - - Ok(Self { - runtime_handle, - initial_config: config, - blockchain, - irregular_state, - mem_pool: MemPool::new(block_gas_limit), - beneficiary, - custom_precompiles, - min_gas_price, - parent_beacon_block_root_generator, - prev_randao_generator, - block_time_offset_seconds, - fork_metadata, - rpc_client, - instance_id: B256::random(), - is_auto_mining, - next_block_base_fee_per_gas, - next_block_timestamp: None, - // Start with 1 to mimic Ganache - next_snapshot_id: 1, - snapshots: BTreeMap::new(), - allow_blocks_with_same_timestamp, - allow_unlimited_contract_size, - verbose_tracing: false, - local_accounts, - filters: HashMap::default(), - last_filter_id: U256::ZERO, - logger, - impersonated_accounts: HashSet::new(), - subscriber_callback, - timer, - call_override, - block_state_cache, - current_state_id, - block_number_to_state_id, - }) - } - - pub fn set_call_override_callback(&mut self, call_override: Option>) { - self.call_override = call_override; - } - - pub fn reset( - &mut self, - fork_config: Option, - ) -> Result<(), CreationError> { - let mut config = self.initial_config.clone(); - config.fork = fork_config; - - let mut reset_instance = Self::new( - self.runtime_handle.clone(), - self.logger.clone(), - self.subscriber_callback.clone(), - self.call_override.clone(), - config, - self.timer.clone(), - )?; - - std::mem::swap(self, &mut reset_instance); - - Ok(()) - } - - /// Retrieves the last pending nonce of the account corresponding to the - /// provided address, if it exists. - pub fn account_next_nonce(&mut self, address: &Address) -> Result { - let state = self.current_state()?; - mempool::account_next_nonce(&self.mem_pool, &*state, address).map_err(Into::into) - } - +impl ProviderData +where + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +{ pub fn accounts(&self) -> impl Iterator { self.local_accounts.keys() } + /// Adds a filter for new pending transactions to the provider. + pub fn add_pending_transaction_filter(&mut self) -> U256 { + let filter_id = self.next_filter_id(); + self.filters.insert( + filter_id, + Filter::new_pending_transaction_filter(IS_SUBSCRIPTION), + ); + filter_id + } + pub fn allow_unlimited_initcode_size(&self) -> bool { self.allow_unlimited_contract_size } - /// Returns whether the miner is mining automatically. - pub fn is_auto_mining(&self) -> bool { - self.is_auto_mining + /// Whether the provider is configured to bail on call failures. + pub fn bail_on_call_failure(&self) -> bool { + self.initial_config.bail_on_call_failure } - pub fn balance( - &mut self, - address: Address, - block_spec: Option<&BlockSpec>, - ) -> Result { - self.execute_in_block_context::>( - block_spec, - move |_blockchain, _block, state| { - Ok(state - .basic(address)? - .map_or(U256::ZERO, |account| account.balance)) - }, - )? + /// Whether the provider is configured to bail on transaction failures. + pub fn bail_on_transaction_failure(&self) -> bool { + self.initial_config.bail_on_transaction_failure } /// Retrieves the gas limit of the next block. @@ -388,6 +261,10 @@ impl ProviderData { self.mem_pool.block_gas_limit().get() } + pub fn coinbase(&self) -> Address { + self.beneficiary + } + /// Returns the default caller. pub fn default_caller(&self) -> Address { self.local_accounts @@ -402,95 +279,468 @@ impl ProviderData { self.fork_metadata.as_ref() } - /// Returns the last block in the blockchain. - pub fn last_block( - &self, - ) -> Result< - Arc>>, - BlockchainError, - > { - self.blockchain.last_block() + pub fn get_filter_changes(&mut self, filter_id: &U256) -> Option { + self.filters.get_mut(filter_id).map(Filter::take_events) } - /// Returns the number of the last block in the blockchain. - pub fn last_block_number(&self) -> u64 { - self.blockchain.last_block_number() + pub fn impersonate_account(&mut self, address: Address) { + self.impersonated_accounts.insert(address); } - /// Adds a filter for new blocks to the provider. - pub fn add_block_filter(&mut self) -> Result { - let block_hash = *self.last_block()?.hash(); + pub fn increase_block_time(&mut self, increment: u64) -> i64 { + self.block_time_offset_seconds += i64::try_from(increment).expect("increment too large"); + self.block_time_offset_seconds + } - let filter_id = self.next_filter_id(); - self.filters.insert( - filter_id, - Filter::new_block_filter(block_hash, IS_SUBSCRIPTION), - ); + pub fn instance_id(&self) -> &B256 { + &self.instance_id + } - Ok(filter_id) + /// Returns whether the miner is mining automatically. + pub fn is_auto_mining(&self) -> bool { + self.is_auto_mining } - /// Adds a filter for new logs to the provider. - pub fn add_log_filter( + pub fn logger_mut( &mut self, - criteria: LogFilter, - ) -> Result { - let logs = self - .blockchain - .logs( - criteria.from_block, - criteria - .to_block - .unwrap_or(self.blockchain.last_block_number()), - &criteria.addresses, - &criteria.normalized_topics, - )? - .iter() - .map(LogOutput::from) - .collect(); + ) -> &mut dyn SyncLogger> { + &mut *self.logger + } - let filter_id = self.next_filter_id(); - self.filters.insert( - filter_id, - Filter::new_log_filter(criteria, logs, IS_SUBSCRIPTION), - ); - Ok(filter_id) + /// Returns the instance's [`MiningConfig`]. + pub fn mining_config(&self) -> &MiningConfig { + &self.initial_config.mining } - /// Adds a filter for new pending transactions to the provider. - pub fn add_pending_transaction_filter(&mut self) -> U256 { - let filter_id = self.next_filter_id(); - self.filters.insert( - filter_id, - Filter::new_pending_transaction_filter(IS_SUBSCRIPTION), - ); - filter_id + /// Returns the instance's network ID. + pub fn network_id(&self) -> String { + self.initial_config.network_id.to_string() } - /// Whether the provider is configured to bail on call failures. - pub fn bail_on_call_failure(&self) -> bool { - self.initial_config.bail_on_call_failure + pub fn pending_transactions(&self) -> impl Iterator { + self.mem_pool.transactions() } - /// Whether the provider is configured to bail on transaction failures. - pub fn bail_on_transaction_failure(&self) -> bool { - self.initial_config.bail_on_transaction_failure + pub fn remove_filter(&mut self, filter_id: &U256) -> bool { + self.remove_filter_impl::(filter_id) } - /// Fetch a block by block spec. - /// Returns `None` if the block spec is `pending`. - /// Returns `ProviderError::InvalidBlockSpec` error if the block spec is a - /// number or a hash and the block isn't found. - /// Returns `ProviderError::InvalidBlockTag` error if the block tag is safe - /// or finalized and block spec is pre-merge. - // `SyncBlock` cannot be simplified further - #[allow(clippy::type_complexity)] - pub fn block_by_block_spec( + pub fn remove_subscription(&mut self, filter_id: &U256) -> bool { + self.remove_filter_impl::(filter_id) + } + + /// Removes the transaction with the provided hash from the mem pool, if it + /// exists. + pub fn remove_pending_transaction( + &mut self, + transaction_hash: &B256, + ) -> Option> { + self.mem_pool.remove_transaction(transaction_hash) + } + + /// Sets whether the miner should mine automatically. + pub fn set_auto_mining(&mut self, enabled: bool) { + self.is_auto_mining = enabled; + } + + pub fn set_call_override_callback(&mut self, call_override: Option>) { + self.call_override = call_override; + } + + /// Sets the coinbase. + pub fn set_coinbase(&mut self, coinbase: Address) { + self.beneficiary = coinbase; + } + + pub fn set_verbose_tracing(&mut self, verbose_tracing: bool) { + self.verbose_tracing = verbose_tracing; + } + + pub fn stop_impersonating_account(&mut self, address: Address) -> bool { + self.impersonated_accounts.remove(&address) + } + + fn add_state_to_cache( + &mut self, + state: Box>, + block_number: u64, + ) -> StateId { + let state_id = self.current_state_id.increment(); + self.block_state_cache.push(state_id, Arc::new(state)); + self.block_number_to_state_id + .insert_mut(block_number, state_id); + state_id + } + + fn next_filter_id(&mut self) -> U256 { + self.last_filter_id = self + .last_filter_id + .checked_add(U256::from(1)) + .expect("filter id starts at zero, so it'll never overflow for U256"); + self.last_filter_id + } + + /// Notifies subscribers to `FilterData::NewPendingTransactions` about the + /// pending transaction with the provided hash. + fn notify_subscribers_about_pending_transaction(&mut self, transaction_hash: &B256) { + for (filter_id, filter) in self.filters.iter_mut() { + if let FilterData::NewPendingTransactions(events) = &mut filter.data { + if filter.is_subscription { + (self.subscriber_callback)(SubscriptionEvent { + filter_id: *filter_id, + result: SubscriptionEventData::NewPendingTransactions(*transaction_hash), + }); + } else { + events.push(*transaction_hash); + } + } + } + } + + /// Notifies subscribers to `FilterData::Logs` and `FilterData::NewHeads` + /// about the mined block. + fn notify_subscribers_about_mined_block( + &mut self, + block_and_total_difficulty: &BlockAndTotalDifficulty< + ChainSpecT, + BlockchainError, + >, + ) -> Result<(), BlockchainError> { + let block = &block_and_total_difficulty.block; + for (filter_id, filter) in self.filters.iter_mut() { + match &mut filter.data { + FilterData::Logs { criteria, logs } => { + let bloom = &block.header().logs_bloom; + if bloom_contains_log_filter(bloom, criteria) { + let receipts = block.transaction_receipts()?; + let new_logs = receipts + .iter() + .flat_map(|receipt| receipt.transaction_logs()); + + let mut filtered_logs = filter_logs(new_logs, criteria); + if filter.is_subscription { + (self.subscriber_callback)(SubscriptionEvent { + filter_id: *filter_id, + result: SubscriptionEventData::Logs(filtered_logs.clone()), + }); + } else { + logs.append(&mut filtered_logs); + } + } + } + FilterData::NewHeads(block_hashes) => { + if filter.is_subscription { + (self.subscriber_callback)(SubscriptionEvent { + filter_id: *filter_id, + result: SubscriptionEventData::NewHeads( + block_and_total_difficulty.clone(), + ), + }); + } else { + block_hashes.push(*block.hash()); + } + } + FilterData::NewPendingTransactions(_) => (), + } + } + + // Remove outdated filters + self.filters.retain(|_, filter| !filter.has_expired()); + + Ok(()) + } + + fn remove_filter_impl(&mut self, filter_id: &U256) -> bool { + if let Some(filter) = self.filters.get(filter_id) { + filter.is_subscription == IS_SUBSCRIPTION && self.filters.remove(filter_id).is_some() + } else { + false + } + } +} + +impl ProviderData +where + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +{ + pub fn get_filter_logs( + &mut self, + filter_id: &U256, + ) -> Result>, ProviderError> { + self.filters + .get_mut(filter_id) + .map(|filter| { + if let Some(events) = filter.take_log_events() { + Ok(events) + } else { + Err(ProviderError::InvalidFilterSubscriptionType { + filter_id: *filter_id, + expected: SubscriptionType::Logs, + actual: filter.data.subscription_type(), + }) + } + }) + .transpose() + } + + pub fn revert_to_snapshot(&mut self, snapshot_id: u64) -> bool { + // Ensure that, if the snapshot exists, we also remove all subsequent snapshots, + // as they can only be used once in Ganache. + let mut removed_snapshots = self.snapshots.split_off(&snapshot_id); + + if let Some(snapshot) = removed_snapshots.remove(&snapshot_id) { + let Snapshot { + block_number, + block_number_to_state_id, + block_time_offset_seconds, + coinbase, + irregular_state, + mem_pool, + next_block_base_fee_per_gas, + next_block_timestamp, + parent_beacon_block_root_generator, + prev_randao_generator, + time, + } = snapshot; + + self.block_number_to_state_id = block_number_to_state_id; + + // We compute a new offset such that: + // now + new_offset == snapshot_date + old_offset + let duration_since_snapshot = Instant::now().duration_since(time); + self.block_time_offset_seconds = block_time_offset_seconds + + i64::try_from(duration_since_snapshot.as_secs()).expect("duration too large"); + + self.beneficiary = coinbase; + self.blockchain + .revert_to_block(block_number) + .expect("Snapshotted block should exist"); + + self.irregular_state = irregular_state; + self.mem_pool = mem_pool; + self.next_block_base_fee_per_gas = next_block_base_fee_per_gas; + self.next_block_timestamp = next_block_timestamp; + self.parent_beacon_block_root_generator = parent_beacon_block_root_generator; + self.prev_randao_generator = prev_randao_generator; + + true + } else { + false + } + } + + pub fn sign( + &self, + address: &Address, + message: Bytes, + ) -> Result> { + match self.local_accounts.get(address) { + Some(secret_key) => Ok(signature::SignatureWithRecoveryId::new( + &message[..], + secret_key, + )?), + None => Err(ProviderError::UnknownAddress { address: *address }), + } + } + + pub fn sign_typed_data_v4( + &self, + address: &Address, + message: &TypedData, + ) -> Result> { + match self.local_accounts.get(address) { + Some(secret_key) => { + let hash = message.eip712_signing_hash()?; + Ok(signature::SignatureWithRecoveryId::new( + RecoveryMessage::Hash(hash), + secret_key, + )?) + } + None => Err(ProviderError::UnknownAddress { address: *address }), + } + } +} + +impl ProviderData +where + ChainSpecT: SyncProviderSpec, + + TimerT: Clone + TimeSinceEpoch, +{ + pub fn new( + runtime_handle: runtime::Handle, + logger: Box>>, + subscriber_callback: Box>, + call_override: Option>, + config: ProviderConfig, + timer: TimerT, + ) -> Result> { + let InitialAccounts { + local_accounts, + genesis_accounts, + } = create_accounts(&config); + + let BlockchainAndState { + blockchain, + fork_metadata, + rpc_client, + state, + irregular_state, + prev_randao_generator, + block_time_offset_seconds, + next_block_base_fee_per_gas, + } = create_blockchain_and_state(runtime_handle.clone(), &config, &timer, genesis_accounts)?; + + let max_cached_states = std::env::var(EDR_MAX_CACHED_STATES_ENV_VAR).map_or_else( + |err| match err { + std::env::VarError::NotPresent => { + Ok(NonZeroUsize::new(DEFAULT_MAX_CACHED_STATES).expect("constant is non-zero")) + } + std::env::VarError::NotUnicode(s) => Err(CreationError::InvalidMaxCachedStates(s)), + }, + |s| { + s.parse() + .map_err(|_err| CreationError::InvalidMaxCachedStates(s.into())) + }, + )?; + let mut block_state_cache = LruCache::new(max_cached_states); + let mut block_number_to_state_id = HashTrieMapSync::default(); + + let current_state_id = StateId::default(); + block_state_cache.push(current_state_id, Arc::new(state)); + block_number_to_state_id.insert_mut(blockchain.last_block_number(), current_state_id); + + let allow_blocks_with_same_timestamp = config.allow_blocks_with_same_timestamp; + let allow_unlimited_contract_size = config.allow_unlimited_contract_size; + let beneficiary = config.coinbase; + let block_gas_limit = config.block_gas_limit; + let is_auto_mining = config.mining.auto_mine; + let min_gas_price = config.min_gas_price; + + let parent_beacon_block_root_generator = if let Some(initial_parent_beacon_block_root) = + &config.initial_parent_beacon_block_root + { + RandomHashGenerator::with_value(*initial_parent_beacon_block_root) + } else { + RandomHashGenerator::with_seed("randomParentBeaconBlockRootSeed") + }; + + let custom_precompiles = { + let mut precompiles = HashMap::new(); + + if config.enable_rip_7212 { + // EIP-7212: secp256r1 P256verify + precompiles.insert(secp256r1::P256VERIFY.0, secp256r1::P256VERIFY.1); + } + + precompiles + }; + + Ok(Self { + runtime_handle, + initial_config: config, + blockchain, + irregular_state, + mem_pool: MemPool::new(block_gas_limit), + beneficiary, + custom_precompiles, + min_gas_price, + parent_beacon_block_root_generator, + prev_randao_generator, + block_time_offset_seconds, + fork_metadata, + rpc_client, + instance_id: B256::random(), + is_auto_mining, + next_block_base_fee_per_gas, + next_block_timestamp: None, + // Start with 1 to mimic Ganache + next_snapshot_id: 1, + snapshots: BTreeMap::new(), + allow_blocks_with_same_timestamp, + allow_unlimited_contract_size, + verbose_tracing: false, + local_accounts, + filters: HashMap::default(), + last_filter_id: U256::ZERO, + logger, + impersonated_accounts: HashSet::new(), + subscriber_callback, + timer, + call_override, + block_state_cache, + current_state_id, + block_number_to_state_id, + }) + } + + /// Retrieves the last pending nonce of the account corresponding to the + /// provided address, if it exists. + pub fn account_next_nonce( + &mut self, + address: &Address, + ) -> Result> { + let state = self.current_state()?; + mempool::account_next_nonce(&self.mem_pool, &*state, address).map_err(Into::into) + } + + /// Adds a filter for new blocks to the provider. + pub fn add_block_filter( + &mut self, + ) -> Result> { + let block_hash = *self.last_block()?.hash(); + + let filter_id = self.next_filter_id(); + self.filters.insert( + filter_id, + Filter::new_block_filter(block_hash, IS_SUBSCRIPTION), + ); + + Ok(filter_id) + } + + /// Adds a filter for new logs to the provider. + pub fn add_log_filter( + &mut self, + criteria: LogFilter, + ) -> Result> { + let logs = self + .blockchain + .logs( + criteria.from_block, + criteria + .to_block + .unwrap_or(self.blockchain.last_block_number()), + &criteria.addresses, + &criteria.normalized_topics, + )? + .iter() + .map(LogOutput::from) + .collect(); + + let filter_id = self.next_filter_id(); + self.filters.insert( + filter_id, + Filter::new_log_filter(criteria, logs, IS_SUBSCRIPTION), + ); + Ok(filter_id) + } + + /// Fetch a block by block spec. + /// Returns `None` if the block spec is `pending`. + /// Returns `ProviderError::InvalidBlockSpec` error if the block spec is a + /// number or a hash and the block isn't found. + /// Returns `ProviderError::InvalidBlockTag` error if the block tag is safe + /// or finalized and block spec is pre-merge. + // `SyncBlock` cannot be simplified further + #[allow(clippy::type_complexity)] + pub fn block_by_block_spec( &self, block_spec: &BlockSpec, ) -> Result< - Option>>>, - ProviderError, + Option>>>, + ProviderError, > { let result = match block_spec { BlockSpec::Number(block_number) => Some( @@ -509,12 +759,12 @@ impl ProviderData { // Matching Hardhat behaviour by returning the last block for finalized and safe. // https://github.com/NomicFoundation/hardhat/blob/b84baf2d9f5d3ea897c06e0ecd5e7084780d8b6c/packages/hardhat-core/src/internal/hardhat-network/provider/modules/eth.ts#L1395 BlockSpec::Tag(tag @ (BlockTag::Finalized | BlockTag::Safe)) => { - if self.spec_id() >= SpecId::MERGE { + if self.evm_spec_id() >= SpecId::MERGE { Some(self.blockchain.last_block()?) } else { return Err(ProviderError::InvalidBlockTag { block_tag: *tag, - spec: self.spec_id(), + spec: self.evm_spec_id(), }); } } @@ -542,605 +792,506 @@ impl ProviderData { Ok(result) } - /// Retrieves the block number for the provided block spec, if it exists. - fn block_number_by_block_spec( - &self, - block_spec: &BlockSpec, - ) -> Result, ProviderError> { - let block_number = match block_spec { - BlockSpec::Number(number) => Some(*number), - BlockSpec::Tag(BlockTag::Earliest) => Some(0), - BlockSpec::Tag(tag @ (BlockTag::Finalized | BlockTag::Safe)) => { - if self.spec_id() >= SpecId::MERGE { - Some(self.blockchain.last_block_number()) - } else { - return Err(ProviderError::InvalidBlockTag { - block_tag: *tag, - spec: self.spec_id(), - }); - } - } - BlockSpec::Tag(BlockTag::Latest) => Some(self.blockchain.last_block_number()), - BlockSpec::Tag(BlockTag::Pending) => None, - BlockSpec::Eip1898(Eip1898BlockSpec::Hash { block_hash, .. }) => { - self.blockchain.block_by_hash(block_hash)?.map_or_else( - || { - Err(ProviderError::InvalidBlockNumberOrHash { - block_spec: block_spec.clone(), - latest_block_number: self.blockchain.last_block_number(), - }) - }, - |block| Ok(Some(block.header().number)), - )? - } - BlockSpec::Eip1898(Eip1898BlockSpec::Number { block_number }) => Some(*block_number), - }; - - Ok(block_number) - } - // `SyncBlock` cannot be simplified further #[allow(clippy::type_complexity)] pub fn block_by_hash( &self, block_hash: &B256, ) -> Result< - Option>>>, - ProviderError, + Option>>>, + ProviderError, > { self.blockchain .block_by_hash(block_hash) .map_err(ProviderError::Blockchain) } - pub fn chain_id(&self) -> u64 { - self.blockchain.chain_id() - } - - pub fn coinbase(&self) -> Address { - self.beneficiary - } - - #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))] - pub fn debug_trace_transaction( - &mut self, - transaction_hash: &B256, - trace_config: DebugTraceConfig, - ) -> Result, ProviderError> { - let block = self - .blockchain - .block_by_transaction_hash(transaction_hash)? - .ok_or_else(|| ProviderError::InvalidTransactionHash(*transaction_hash))?; - - let header = block.header(); - let block_spec = Some(BlockSpec::Number(header.number)); - - let cfg_env = self.create_evm_config(block_spec.as_ref())?; - - let transactions = block.transactions().to_vec(); - - let prev_block_number = block.header().number - 1; - let prev_block_spec = Some(BlockSpec::Number(prev_block_number)); - let verbose_tracing = self.verbose_tracing; + pub fn gas_price(&self) -> Result> { + const PRE_EIP_1559_GAS_PRICE: u64 = 8_000_000_000; + const SUGGESTED_PRIORITY_FEE_PER_GAS: u64 = 1_000_000_000; - self.execute_in_block_context( - prev_block_spec.as_ref(), - |blockchain, _prev_block, state| { - let block_env = BlockEnv { - number: U256::from(header.number), - coinbase: header.beneficiary, - timestamp: U256::from(header.timestamp), - gas_limit: U256::from(header.gas_limit), - basefee: header.base_fee_per_gas.unwrap_or_default(), - difficulty: U256::from(header.difficulty), - prevrandao: if cfg_env.spec_id >= SpecId::MERGE { - Some(header.mix_hash) - } else { - None - }, - blob_excess_gas_and_price: header - .blob_gas - .as_ref() - .map(|BlobGas { excess_gas, .. }| BlobExcessGasAndPrice::new(*excess_gas)), - }; + if let Some(next_block_gas_fee_per_gas) = self.next_block_base_fee_per_gas()? { + Ok(next_block_gas_fee_per_gas + U256::from(SUGGESTED_PRIORITY_FEE_PER_GAS)) + } else { + // We return a hardcoded value for networks without EIP-1559 + Ok(U256::from(PRE_EIP_1559_GAS_PRICE)) + } + } - debug_trace_transaction( - blockchain, - state.clone(), - cfg_env, - trace_config, - block_env, - transactions, - transaction_hash, - verbose_tracing, - ) - .map_err(ProviderError::DebugTrace) - }, - )? + pub fn logs(&self, filter: LogFilter) -> Result, ProviderError> { + self.blockchain + .logs( + filter.from_block, + filter + .to_block + .unwrap_or(self.blockchain.last_block_number()), + &filter.addresses, + &filter.normalized_topics, + ) + .map_err(ProviderError::Blockchain) } - pub fn debug_trace_call( + /// Resets the provider to its initial state, with a modified + /// [`ForkConfig`]. + pub fn reset( &mut self, - transaction: transaction::Signed, - block_spec: &BlockSpec, - trace_config: DebugTraceConfig, - ) -> Result, ProviderError> { - let cfg_env = self.create_evm_config(Some(block_spec))?; + fork_config: Option, + ) -> Result<(), CreationError> { + let mut config = self.initial_config.clone(); + config.fork = fork_config; - let mut tracer = Eip3155AndRawTracers::new(trace_config, self.verbose_tracing); - let precompiles = self.custom_precompiles.clone(); + let mut reset_instance = Self::new( + self.runtime_handle.clone(), + self.logger.clone(), + self.subscriber_callback.clone(), + self.call_override.clone(), + config, + self.timer.clone(), + )?; - self.execute_in_block_context(Some(block_spec), |blockchain, block, state| { - let result = run_call(RunCallArgs { - blockchain, - header: block.header(), - state, - state_overrides: &StateOverrides::default(), - cfg_env: cfg_env.clone(), - transaction, - precompiles: &precompiles, - debug_context: Some(DebugContext { - data: &mut tracer, - register_handles_fn: register_eip_3155_and_raw_tracers_handles, - }), - })?; + std::mem::swap(self, &mut reset_instance); - Ok(execution_result_to_debug_result(result, tracer)) - })? + Ok(()) } - /// Estimate the gas cost of a transaction. Matches Hardhat behavior. - pub fn estimate_gas( + pub fn set_account_storage_slot( &mut self, - transaction: transaction::Signed, - block_spec: &BlockSpec, - ) -> Result { - let cfg_env = self.create_evm_config(Some(block_spec))?; - // Minimum gas cost that is required for transaction to be included in - // a block - let minimum_cost = transaction::initial_cost(&transaction, self.spec_id()); + address: Address, + index: U256, + value: U256, + ) -> Result<(), ProviderError> { + // We clone to automatically revert in case of subsequent errors. + let mut modified_state = (*self.current_state()?).clone(); + let old_value = modified_state.set_account_storage_slot(address, index, value)?; - let state_overrides = StateOverrides::default(); + let slot = EvmStorageSlot::new_changed(old_value, value); + let account_info = modified_state.basic(address).and_then(|mut account_info| { + // Retrieve the code if it's not empty. This is needed for the irregular state. + if let Some(account_info) = &mut account_info { + if account_info.code_hash != KECCAK_EMPTY { + account_info.code = Some(modified_state.code_by_hash(account_info.code_hash)?); + } + } - let mut debugger = Debugger::with_mocker( - Mocker::new(self.call_override.clone()), - self.verbose_tracing, - ); + Ok(account_info) + })?; - let precompiles = self.custom_precompiles.clone(); - self.execute_in_block_context(Some(block_spec), |blockchain, block, state| { - let header = block.header(); + let state_root = modified_state.state_root()?; - // Measure the gas used by the transaction with optional limit from call request - // defaulting to block limit. Report errors from initial call as if from - // `eth_call`. - let result = call::run_call(RunCallArgs { - blockchain, - header, - state, - state_overrides: &state_overrides, - cfg_env: cfg_env.clone(), - transaction: transaction.clone(), - precompiles: &precompiles, - debug_context: Some(DebugContext { - data: &mut debugger, - register_handles_fn: register_debugger_handles, - }), - })?; + let block_number = self.blockchain.last_block_number(); + self.irregular_state + .state_override_at_block_number(block_number) + .or_insert_with(|| StateOverride::with_state_root(state_root)) + .diff + .apply_storage_change(address, index, slot, account_info); - let Debugger { - console_logger, - mut trace_collector, - .. - } = debugger; + self.add_state_to_cache(modified_state, block_number); - let mut initial_estimation = match result { - ExecutionResult::Success { gas_used, .. } => Ok(gas_used), - ExecutionResult::Revert { output, .. } => Err(TransactionFailure::revert( - output, - None, - trace_collector - .traces() - .first() - .expect("Must have a trace") - .clone(), - )), - ExecutionResult::Halt { reason, .. } => Err(TransactionFailure::halt( - reason, - None, - trace_collector - .traces() - .first() - .expect("Must have a trace") - .clone(), - )), - } - .map_err(|failure| EstimateGasFailure { - console_log_inputs: console_logger.into_encoded_messages(), - transaction_failure: TransactionFailureWithTraces { - traces: vec![failure.solidity_trace.clone()], - failure, - }, - })?; + Ok(()) + } - // Ensure that the initial estimation is at least the minimum cost + 1. - if initial_estimation <= minimum_cost { - initial_estimation = minimum_cost + 1; - } + pub fn set_balance( + &mut self, + address: Address, + balance: U256, + ) -> Result<(), ProviderError> { + let mut modified_state = (*self.current_state()?).clone(); + let account_info = modified_state.modify_account( + address, + AccountModifierFn::new(Box::new(move |account_balance, _, _| { + *account_balance = balance; + })), + )?; - // Test if the transaction would be successful with the initial estimation - let success = gas::check_gas_limit(CheckGasLimitArgs { - blockchain, - header, - state, - state_overrides: &state_overrides, - cfg_env: cfg_env.clone(), - transaction: transaction.clone(), - gas_limit: initial_estimation, - precompiles: &precompiles, - trace_collector: &mut trace_collector, - })?; + let state_root = modified_state.state_root()?; - // Return the initial estimation if it was successful - if success { - return Ok(EstimateGasResult { - estimation: initial_estimation, - traces: trace_collector.into_traces(), - }); - } + self.mem_pool.update(&modified_state)?; - // Correct the initial estimation if the transaction failed with the actually - // used gas limit. This can happen if the execution logic is based - // on the available gas. - let estimation = gas::binary_search_estimation(BinarySearchEstimationArgs { - blockchain, - header, - state, - state_overrides: &state_overrides, - cfg_env: cfg_env.clone(), - transaction, - lower_bound: initial_estimation, - upper_bound: header.gas_limit, - precompiles: &precompiles, - trace_collector: &mut trace_collector, - })?; + let block_number = self.blockchain.last_block_number(); + self.irregular_state + .state_override_at_block_number(block_number) + .or_insert_with(|| StateOverride::with_state_root(state_root)) + .diff + .apply_account_change(address, account_info.clone()); - let traces = trace_collector.into_traces(); - Ok(EstimateGasResult { estimation, traces }) - })? + self.add_state_to_cache(modified_state, block_number); + + Ok(()) } - // Matches Hardhat implementation - pub fn fee_history( + /// Sets the gas limit used for mining new blocks. + pub fn set_block_gas_limit( &mut self, - block_count: u64, - newest_block_spec: &BlockSpec, - percentiles: Option>, - ) -> Result { - if self.spec_id() < SpecId::LONDON { - return Err(ProviderError::UnmetHardfork { - actual: self.spec_id(), - minimum: SpecId::LONDON, - }); - } + gas_limit: NonZeroU64, + ) -> Result<(), ProviderError> { + let state = self.current_state()?; + self.mem_pool + .set_block_gas_limit(&*state, gas_limit) + .map_err(ProviderError::State) + } - let latest_block_number = self.last_block_number(); - let pending_block_number = latest_block_number + 1; - let newest_block_number = self - .block_by_block_spec(newest_block_spec)? - // None if pending block - .map_or(pending_block_number, |block| block.header().number); - let oldest_block_number = if newest_block_number < block_count { - 0 - } else { - newest_block_number - block_count + 1 - }; - let last_block_number = newest_block_number + 1; + pub fn set_code( + &mut self, + address: Address, + code: Bytes, + ) -> Result<(), ProviderError> { + let code = Bytecode::new_raw(code.clone()); + let irregular_code = code.clone(); - let pending_block = if last_block_number >= pending_block_number { - let DebugMineBlockResultAndState { block, .. } = self.mine_pending_block()?; - Some(block) - } else { - None - }; + // We clone to automatically revert in case of subsequent errors. + let mut modified_state = (*self.current_state()?).clone(); + let mut account_info = modified_state.modify_account( + address, + AccountModifierFn::new(Box::new(move |_, _, account_code| { + *account_code = Some(code.clone()); + })), + )?; - let mut result = FeeHistoryResult::new(oldest_block_number); + // The code was stripped from the account, so we need to re-add it for the + // irregular state. + account_info.code = Some(irregular_code.clone()); - let mut reward_and_percentile = percentiles.and_then(|percentiles| { - if percentiles.is_empty() { - None - } else { - Some((Vec::default(), percentiles)) - } - }); + let state_root = modified_state.state_root()?; - let range_includes_remote_blocks = self.fork_metadata.as_ref().map_or(false, |metadata| { - oldest_block_number <= metadata.fork_block_number - }); + let block_number = self.blockchain.last_block_number(); + self.irregular_state + .state_override_at_block_number(block_number) + .or_insert_with(|| StateOverride::with_state_root(state_root)) + .diff + .apply_account_change(address, account_info.clone()); - if range_includes_remote_blocks { - let last_remote_block = cmp::min( - self.fork_metadata - .as_ref() - .expect("we checked that there is a fork") - .fork_block_number, - last_block_number, - ); - let remote_block_count = last_remote_block - oldest_block_number + 1; + self.add_state_to_cache(modified_state, block_number); - let rpc_client = self - .rpc_client - .as_ref() - .expect("we checked that there is a fork"); - let FeeHistoryResult { - oldest_block: _, - base_fee_per_gas, - gas_used_ratio, - reward: remote_reward, - } = tokio::task::block_in_place(|| { - self.runtime_handle.block_on( - rpc_client.fee_history( - remote_block_count, - newest_block_spec.clone(), - reward_and_percentile - .as_ref() - .map(|(_, percentiles)| percentiles.clone()), - ), - ) - })?; + Ok(()) + } - result.base_fee_per_gas = base_fee_per_gas; - result.gas_used_ratio = gas_used_ratio; - if let Some((ref mut reward, _)) = reward_and_percentile.as_mut() { - if let Some(remote_reward) = remote_reward { - *reward = remote_reward; - } - } + pub fn set_min_gas_price( + &mut self, + min_gas_price: U256, + ) -> Result<(), ProviderError> { + if self.evm_spec_id() >= SpecId::LONDON { + return Err(ProviderError::SetMinGasPriceUnsupported); } - let first_local_block = if range_includes_remote_blocks { - cmp::min( - self.fork_metadata - .as_ref() - .expect("we checked that there is a fork") - .fork_block_number, - last_block_number, - ) + 1 - } else { - oldest_block_number - }; + self.min_gas_price = min_gas_price; - for block_number in first_local_block..=last_block_number { - if block_number < pending_block_number { - let block = self - .blockchain - .block_by_number(block_number)? - .expect("Block must exist as i is at most the last block number"); + Ok(()) + } - let header = block.header(); - result - .base_fee_per_gas - .push(header.base_fee_per_gas.unwrap_or(U256::ZERO)); + /// Sets the next block's base fee per gas. + pub fn set_next_block_base_fee_per_gas( + &mut self, + base_fee_per_gas: U256, + ) -> Result<(), ProviderError> { + let spec_id = self.evm_spec_id(); + if spec_id < SpecId::LONDON { + return Err(ProviderError::SetNextBlockBaseFeePerGasUnsupported { spec_id }); + } - if block_number < last_block_number { - result - .gas_used_ratio - .push(gas_used_ratio(header.gas_used, header.gas_limit)); + self.next_block_base_fee_per_gas = Some(base_fee_per_gas); - if let Some((ref mut reward, percentiles)) = reward_and_percentile.as_mut() { - reward.push(compute_rewards(&block, percentiles)?); - } - } - } else if block_number == pending_block_number { - let next_block_base_fee_per_gas = self - .next_block_base_fee_per_gas()? - .expect("We checked that EIP-1559 is active"); - result.base_fee_per_gas.push(next_block_base_fee_per_gas); + Ok(()) + } - if block_number < last_block_number { - let block = pending_block.as_ref().expect("We mined the pending block"); - let header = block.header(); - result - .gas_used_ratio - .push(gas_used_ratio(header.gas_used, header.gas_limit)); + /// Set the next block timestamp. + pub fn set_next_block_timestamp( + &mut self, + timestamp: u64, + ) -> Result> { + let latest_block = self.blockchain.last_block()?; + let latest_block_header = latest_block.header(); - if let Some((ref mut reward, percentiles)) = reward_and_percentile.as_mut() { - // We don't compute this for the pending block, as there's no - // effective miner fee yet. - reward.push(percentiles.iter().map(|_| U256::ZERO).collect()); - } - } - } else if block_number == pending_block_number + 1 { - let block = pending_block.as_ref().expect("We mined the pending block"); - result - .base_fee_per_gas - .push(calculate_next_base_fee_per_gas::( - self.spec_id(), - block.header(), - )); + match timestamp.cmp(&latest_block_header.timestamp) { + Ordering::Less => Err(ProviderError::TimestampLowerThanPrevious { + proposed: timestamp, + previous: latest_block_header.timestamp, + }), + Ordering::Equal if !self.allow_blocks_with_same_timestamp => { + Err(ProviderError::TimestampEqualsPrevious { + proposed: timestamp, + }) + } + Ordering::Equal | Ordering::Greater => { + self.next_block_timestamp = Some(timestamp); + Ok(timestamp) } } + } - if let Some((reward, _)) = reward_and_percentile { - result.reward = Some(reward); + /// Sets the next block's prevrandao. + pub fn set_next_prev_randao( + &mut self, + prev_randao: B256, + ) -> Result<(), ProviderError> { + let spec_id = self.evm_spec_id(); + if spec_id < SpecId::MERGE { + return Err(ProviderError::SetNextPrevRandaoUnsupported { spec_id }); } - Ok(result) - } - - pub fn gas_price(&self) -> Result { - const PRE_EIP_1559_GAS_PRICE: u64 = 8_000_000_000; - const SUGGESTED_PRIORITY_FEE_PER_GAS: u64 = 1_000_000_000; + self.prev_randao_generator.set_next(prev_randao); - if let Some(next_block_gas_fee_per_gas) = self.next_block_base_fee_per_gas()? { - Ok(next_block_gas_fee_per_gas + U256::from(SUGGESTED_PRIORITY_FEE_PER_GAS)) - } else { - // We return a hardcoded value for networks without EIP-1559 - Ok(U256::from(PRE_EIP_1559_GAS_PRICE)) - } + Ok(()) } - pub fn get_code( + pub fn set_nonce( &mut self, address: Address, - block_spec: Option<&BlockSpec>, - ) -> Result { - self.execute_in_block_context(block_spec, move |_blockchain, _block, state| { - let code = state - .basic(address)? - .map_or(Ok(Bytes::new()), |account_info| { - state.code_by_hash(account_info.code_hash).map(|bytecode| { - // The `Bytecode` REVM struct pad the bytecode with 33 bytes of 0s for the - // `Checked` and `Analysed` variants. `Bytecode::original_bytes` returns - // unpadded version. - bytecode.original_bytes() - }) - })?; + nonce: u64, + ) -> Result<(), ProviderError> { + if mempool::has_transactions(&self.mem_pool) { + return Err(ProviderError::SetAccountNonceWithPendingTransactions); + } - Ok(code) - })? - } + let previous_nonce = self + .current_state()? + .basic(address)? + .map_or(0, |account| account.nonce); - pub fn get_filter_changes(&mut self, filter_id: &U256) -> Option { - self.filters.get_mut(filter_id).map(Filter::take_events) + if nonce < previous_nonce { + return Err(ProviderError::SetAccountNonceLowerThanCurrent { + previous: previous_nonce, + proposed: nonce, + }); + } + + // We clone to automatically revert in case of subsequent errors. + let mut modified_state = (*self.current_state()?).clone(); + let account_info = modified_state.modify_account( + address, + AccountModifierFn::new(Box::new(move |_, account_nonce, _| *account_nonce = nonce)), + )?; + + let state_root = modified_state.state_root()?; + + self.mem_pool.update(&modified_state)?; + + let block_number = self.last_block_number(); + self.irregular_state + .state_override_at_block_number(block_number) + .or_insert_with(|| StateOverride::with_state_root(state_root)) + .diff + .apply_account_change(address, account_info.clone()); + + self.add_state_to_cache(modified_state, block_number); + + Ok(()) } - pub fn get_filter_logs( - &mut self, - filter_id: &U256, - ) -> Result>, ProviderError> { - self.filters - .get_mut(filter_id) - .map(|filter| { - if let Some(events) = filter.take_log_events() { - Ok(events) - } else { - Err(ProviderError::InvalidFilterSubscriptionType { - filter_id: *filter_id, - expected: SubscriptionType::Logs, - actual: filter.data.subscription_type(), - }) - } - }) - .transpose() + pub fn sign_transaction_request( + &self, + transaction_request: TransactionRequestAndSender, + ) -> Result> { + let TransactionRequestAndSender { request, sender } = transaction_request; + + if self.impersonated_accounts.contains(&sender) { + let signed_transaction = request.fake_sign(sender); + transaction::validate(signed_transaction, self.evm_spec_id()) + .map_err(ProviderError::TransactionCreationError) + } else { + let secret_key = self + .local_accounts + .get(&sender) + .ok_or(ProviderError::UnknownAddress { address: sender })?; + + // SAFETY: We know the secret key belongs to the sender, as we retrieved it from + // `local_accounts`. + let signed_transaction = + unsafe { request.sign_for_sender_unchecked(secret_key, sender) }?; + + transaction::validate(signed_transaction, self.evm_spec_id()) + .map_err(ProviderError::TransactionCreationError) + } } - pub fn get_storage_at( - &mut self, - address: Address, - index: U256, - block_spec: Option<&BlockSpec>, - ) -> Result { - self.execute_in_block_context::>( - block_spec, - move |_blockchain, _block, state| Ok(state.storage(address, index)?), - )? + pub fn total_difficulty_by_hash( + &self, + hash: &B256, + ) -> Result, ProviderError> { + self.blockchain + .total_difficulty_by_hash(hash) + .map_err(ProviderError::Blockchain) } - pub fn get_transaction_count( - &mut self, - address: Address, - block_spec: Option<&BlockSpec>, - ) -> Result { - self.execute_in_block_context::>( - block_spec, - move |_blockchain, _block, state| { - let nonce = state - .basic(address)? - .map_or(0, |account_info| account_info.nonce); + /// Get a transaction by hash from the blockchain or from the mempool if + /// it's not mined yet. + pub fn transaction_by_hash( + &self, + hash: &B256, + ) -> Result>, ProviderError> { + let transaction = if let Some(tx) = self.mem_pool.transaction_by_hash(hash) { + Some(TransactionAndBlock { + transaction: tx.pending().clone(), + block_data: None, + is_pending: true, + }) + } else if let Some(block) = self.blockchain.block_by_transaction_hash(hash)? { + let tx_index_u64 = self + .blockchain + .receipt_by_transaction_hash(hash)? + .expect("If the transaction was inserted in a block, it must have a receipt") + .transaction_index; + let tx_index = + usize::try_from(tx_index_u64).expect("Indices cannot be larger than usize::MAX"); - Ok(nonce) - }, - )? - } + let transaction = block + .transactions() + .get(tx_index) + .expect("Transaction index must be valid, since it's from the receipt.") + .clone(); - pub fn impersonate_account(&mut self, address: Address) { - self.impersonated_accounts.insert(address); - } + Some(TransactionAndBlock { + transaction, + block_data: Some(BlockDataForTransaction { + block, + transaction_index: tx_index_u64, + }), + is_pending: false, + }) + } else { + None + }; - pub fn increase_block_time(&mut self, increment: u64) -> i64 { - self.block_time_offset_seconds += i64::try_from(increment).expect("increment too large"); - self.block_time_offset_seconds + Ok(transaction) } - pub fn instance_id(&self) -> &B256 { - &self.instance_id + pub fn transaction_receipt( + &self, + transaction_hash: &B256, + ) -> Result>>, ProviderError> { + self.blockchain + .receipt_by_transaction_hash(transaction_hash) + .map_err(ProviderError::Blockchain) } - #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] - pub fn interval_mine(&mut self) -> Result { - let result = self.mine_and_commit_block(BlockOptions::default())?; + fn add_pending_transaction( + &mut self, + transaction: ChainSpecT::Transaction, + ) -> Result> { + let transaction_hash = *transaction.transaction_hash(); - self.logger - .log_interval_mined(self.spec_id(), &result) - .map_err(ProviderError::Logger)?; + let state = self.current_state()?; + // Handles validation + self.mem_pool.add_transaction(&*state, transaction)?; - Ok(true) - } + self.notify_subscribers_about_pending_transaction(&transaction_hash); - pub fn logger_mut( - &mut self, - ) -> &mut dyn SyncLogger> { - &mut *self.logger + Ok(transaction_hash) } - pub fn logs(&self, filter: LogFilter) -> Result, ProviderError> { - self.blockchain - .logs( - filter.from_block, - filter - .to_block - .unwrap_or(self.blockchain.last_block_number()), - &filter.addresses, - &filter.normalized_topics, - ) - .map_err(ProviderError::Blockchain) + /// Retrieves the block number for the provided block spec, if it exists. + fn block_number_by_block_spec( + &self, + block_spec: &BlockSpec, + ) -> Result, ProviderError> { + let block_number = match block_spec { + BlockSpec::Number(number) => Some(*number), + BlockSpec::Tag(BlockTag::Earliest) => Some(0), + BlockSpec::Tag(tag @ (BlockTag::Finalized | BlockTag::Safe)) => { + if self.evm_spec_id() >= SpecId::MERGE { + Some(self.blockchain.last_block_number()) + } else { + return Err(ProviderError::InvalidBlockTag { + block_tag: *tag, + spec: self.evm_spec_id(), + }); + } + } + BlockSpec::Tag(BlockTag::Latest) => Some(self.blockchain.last_block_number()), + BlockSpec::Tag(BlockTag::Pending) => None, + BlockSpec::Eip1898(Eip1898BlockSpec::Hash { block_hash, .. }) => { + self.blockchain.block_by_hash(block_hash)?.map_or_else( + || { + Err(ProviderError::InvalidBlockNumberOrHash { + block_spec: block_spec.clone(), + latest_block_number: self.blockchain.last_block_number(), + }) + }, + |block| Ok(Some(block.header().number)), + )? + } + BlockSpec::Eip1898(Eip1898BlockSpec::Number { block_number }) => Some(*block_number), + }; + + Ok(block_number) } - pub fn make_snapshot(&mut self) -> u64 { - let id = self.next_snapshot_id; - self.next_snapshot_id += 1; + /// Creates a configuration, taking into the hardfork at the provided + /// `BlockSpec`. If none is provided, assumes the hardfork for newly + /// mined blocks. + fn create_evm_config( + &self, + block_spec: Option<&BlockSpec>, + ) -> Result, ProviderError> { + let block_number = block_spec + .map(|block_spec| self.block_number_by_block_spec(block_spec)) + .transpose()? + .flatten(); - let snapshot = Snapshot { - block_number: self.blockchain.last_block_number(), - block_number_to_state_id: self.block_number_to_state_id.clone(), - block_time_offset_seconds: self.block_time_offset_seconds, - coinbase: self.beneficiary, - irregular_state: self.irregular_state.clone(), - mem_pool: self.mem_pool.clone(), - next_block_base_fee_per_gas: self.next_block_base_fee_per_gas, - next_block_timestamp: self.next_block_timestamp, - parent_beacon_block_root_generator: self.parent_beacon_block_root_generator.clone(), - prev_randao_generator: self.prev_randao_generator.clone(), - time: Instant::now(), + let spec_id = if let Some(block_number) = block_number { + self.blockchain.spec_at_block_number(block_number)? + } else { + self.blockchain.spec_id() }; - self.snapshots.insert(id, snapshot); - id + let mut cfg_env = CfgEnv::default(); + cfg_env.chain_id = self.blockchain.chain_id(); + cfg_env.limit_contract_code_size = if self.allow_unlimited_contract_size { + Some(usize::MAX) + } else { + None + }; + cfg_env.disable_eip3607 = true; + + Ok(CfgEnvWithEvmWiring::::new(cfg_env, spec_id)) } - /// Mines a block with the provided options, using transactions in the - /// mempool, and commits it to the blockchain. - pub fn mine_and_commit_block( + fn current_state( &mut self, - options: BlockOptions, - ) -> Result>, ProviderError> - { - self.mine_and_commit_block_impl(Self::mine_block_with_mem_pool, options) + ) -> Result>>, ProviderError> { + self.get_or_compute_state(self.last_block_number()) + } + + fn get_or_compute_state( + &mut self, + block_number: u64, + ) -> Result>>, ProviderError> { + if let Some(state_id) = self.block_number_to_state_id.get(&block_number) { + // We cannot use `LruCache::try_get_or_insert`, because it needs &mut self, but + // we would need &self in the callback to reference the blockchain. + if let Some(state) = self.block_state_cache.get(state_id) { + return Ok(state.clone()); + } + }; + + let state = self + .blockchain + .state_at_block_number(block_number, self.irregular_state.state_overrides())?; + let state_id = self.add_state_to_cache(state, block_number); + Ok(self + .block_state_cache + .get(&state_id) + // State must exist, since we just inserted it, and we have exclusive access to + // the cache due to &mut self. + .expect("State must exist") + .clone()) } fn mine_and_commit_block_impl( &mut self, mine_fn: impl FnOnce( - &mut ProviderData, - &CfgEnvWithEvmWiring, + &mut ProviderData, + &CfgEnvWithEvmWiring, BlockOptions, - &mut Debugger, - ) - -> Result, ProviderError>, + &mut Debugger, + ) -> Result< + MineBlockResultAndState, + ProviderError, + >, mut options: BlockOptions, - ) -> Result>, ProviderError> - { + ) -> Result< + DebugMineBlockResult>, + ProviderError, + > { let (block_timestamp, new_offset) = self.next_block_timestamp(options.timestamp)?; options.timestamp = Some(block_timestamp); @@ -1183,756 +1334,762 @@ impl ProviderData { }) } - /// Mines `number_of_blocks` blocks with the provided `interval` between - /// them. - pub fn mine_and_commit_blocks( - &mut self, - number_of_blocks: u64, - interval: u64, - ) -> Result>>, ProviderError> - { - // There should be at least 2 blocks left for the reservation to work, - // because we always mine a block after it. But here we use a bigger - // number to err on the side of safety. - const MINIMUM_RESERVABLE_BLOCKS: u64 = 6; - - if number_of_blocks == 0 { - return Ok(Vec::new()); - } - - let mine_block_with_interval = |data: &mut ProviderData, - mined_blocks: &mut Vec< - DebugMineBlockResult>, - >| - -> Result<(), ProviderError> { - let previous_timestamp = mined_blocks - .last() - .expect("at least one block was mined") - .block - .header() - .timestamp; - - let options = BlockOptions { - timestamp: Some(previous_timestamp + interval), - ..BlockOptions::default() - }; - - let mined_block = data.mine_and_commit_block(options)?; - mined_blocks.push(mined_block); - - Ok(()) - }; - - // Limit the pre-allocated capacity based on the minimum reservable number of - // blocks to avoid too large allocations. - let mut mined_blocks = Vec::with_capacity( - usize::try_from(number_of_blocks.min(2 * MINIMUM_RESERVABLE_BLOCKS)) - .expect("number of blocks exceeds {u64::MAX}"), - ); + /// Mines a block using the provided options. If an option has not been + /// specified, it will be set using the provider's configuration values. + fn mine_block( + &mut self, + mine_fn: impl FnOnce( + &mut ProviderData, + &CfgEnvWithEvmWiring, + BlockOptions, + &mut Debugger, + ) -> Result< + MineBlockResultAndState, + ProviderError, + >, + mut options: BlockOptions, + ) -> Result, ProviderError> + { + options.base_fee = options.base_fee.or(self.next_block_base_fee_per_gas); + options.beneficiary = Some(options.beneficiary.unwrap_or(self.beneficiary)); + options.gas_limit = Some(options.gas_limit.unwrap_or_else(|| self.block_gas_limit())); - // we always mine the first block, and we don't apply the interval for it - mined_blocks.push(self.mine_and_commit_block(BlockOptions::default())?); + let evm_config = self.create_evm_config(None)?; - while u64::try_from(mined_blocks.len()).expect("usize cannot be larger than u128") - < number_of_blocks - && self.mem_pool.has_pending_transactions() - { - mine_block_with_interval(self, &mut mined_blocks)?; + if options.mix_hash.is_none() && evm_config.spec_id.into() >= SpecId::MERGE { + options.mix_hash = Some(self.prev_randao_generator.next_value()); } - // If there is at least one remaining block, we mine one. This way, we - // guarantee that there's an empty block immediately before and after the - // reservation. This makes the logging easier to get right. - if u64::try_from(mined_blocks.len()).expect("usize cannot be larger than u128") - < number_of_blocks - { - mine_block_with_interval(self, &mut mined_blocks)?; + if evm_config.spec_id.into() >= SpecId::CANCUN { + options.parent_beacon_block_root = options + .parent_beacon_block_root + .or_else(|| Some(self.parent_beacon_block_root_generator.next_value())); } - let remaining_blocks = number_of_blocks - - u64::try_from(mined_blocks.len()).expect("usize cannot be larger than u128"); + let mut debugger = Debugger::with_mocker( + Mocker::new(self.call_override.clone()), + self.verbose_tracing, + ); - if remaining_blocks < MINIMUM_RESERVABLE_BLOCKS { - for _ in 0..remaining_blocks { - mine_block_with_interval(self, &mut mined_blocks)?; - } - } else { - let current_state = (*self.current_state()?).clone(); + let result = mine_fn(self, &evm_config, options, &mut debugger)?; - self.blockchain - .reserve_blocks(remaining_blocks - 1, interval)?; + let Debugger { + console_logger, + trace_collector, + .. + } = debugger; - // Ensure there is a cache entry for the last reserved block, to avoid - // recomputation - self.add_state_to_cache(current_state, self.last_block_number()); + let traces = trace_collector.into_traces(); - let previous_timestamp = self.blockchain.last_block()?.header().timestamp; - let options = BlockOptions { - timestamp: Some(previous_timestamp + interval), - ..BlockOptions::default() - }; + Ok(DebugMineBlockResultAndState::new( + result, + traces, + console_logger.into_encoded_messages(), + )) + } - let mined_block = self.mine_and_commit_block(options)?; - mined_blocks.push(mined_block); - } + /// Get the timestamp for the next block. + /// Ported from + fn next_block_timestamp( + &self, + timestamp: Option, + ) -> Result<(u64, Option), ProviderError> { + let latest_block = self.blockchain.last_block()?; + let latest_block_header = latest_block.header(); - mined_blocks.shrink_to_fit(); + let current_timestamp = + i64::try_from(self.timer.since_epoch()).expect("timestamp too large"); - Ok(mined_blocks) - } + let (mut block_timestamp, mut new_offset) = if let Some(timestamp) = timestamp { + timestamp.checked_sub(latest_block_header.timestamp).ok_or( + ProviderError::TimestampLowerThanPrevious { + proposed: timestamp, + previous: latest_block_header.timestamp, + }, + )?; - pub fn network_id(&self) -> String { - self.initial_config.network_id.to_string() - } + let offset = i64::try_from(timestamp).expect("timestamp too large") - current_timestamp; + (timestamp, Some(offset)) + } else if let Some(next_block_timestamp) = self.next_block_timestamp { + let offset = i64::try_from(next_block_timestamp).expect("timestamp too large") + - current_timestamp; - /// Calculates the next block's base fee per gas. - pub fn next_block_base_fee_per_gas( - &self, - ) -> Result, BlockchainError> { - if self.spec_id() < SpecId::LONDON { - return Ok(None); - } + (next_block_timestamp, Some(offset)) + } else { + let next_timestamp = u64::try_from(current_timestamp + self.block_time_offset_seconds) + .expect("timestamp must be positive"); - self.next_block_base_fee_per_gas - .map_or_else( - || { - let last_block = self.last_block()?; + (next_timestamp, None) + }; - Ok(calculate_next_base_fee_per_gas::( - self.spec_id(), - last_block.header(), - )) - }, - Ok, - ) - .map(Some) + let timestamp_needs_increase = block_timestamp == latest_block_header.timestamp + && !self.allow_blocks_with_same_timestamp; + if timestamp_needs_increase { + block_timestamp += 1; + if new_offset.is_none() { + new_offset = Some(self.block_time_offset_seconds + 1); + } + } + + Ok((block_timestamp, new_offset)) } - /// Calculates the next block's base fee per blob gas. - pub fn next_block_base_fee_per_blob_gas( - &self, - ) -> Result, BlockchainError> { - if self.spec_id() < SpecId::CANCUN { - return Ok(None); + fn validate_auto_mine_transaction( + &mut self, + transaction: &ChainSpecT::Transaction, + ) -> Result<(), ProviderError> { + let next_nonce = { self.account_next_nonce(transaction.caller())? }; + + match transaction.nonce().cmp(&next_nonce) { + Ordering::Less => { + return Err(ProviderError::AutoMineNonceTooLow { + expected: next_nonce, + actual: transaction.nonce(), + }) + } + Ordering::Equal => (), + Ordering::Greater => { + return Err(ProviderError::AutoMineNonceTooHigh { + expected: next_nonce, + actual: transaction.nonce(), + }) + } } - let last_block = self.last_block()?; - let base_fee = calculate_next_base_fee_per_blob_gas(last_block.header()); + let max_priority_fee_per_gas = transaction + .max_priority_fee_per_gas() + .unwrap_or_else(|| transaction.gas_price()); - Ok(Some(U256::from(base_fee))) - } + if *max_priority_fee_per_gas < self.min_gas_price { + return Err(ProviderError::AutoMinePriorityFeeTooLow { + expected: self.min_gas_price, + actual: *max_priority_fee_per_gas, + }); + } - /// Calculates the gas price for the next block. - pub fn next_gas_price(&self) -> Result> { - if let Some(next_block_base_fee_per_gas) = self.next_block_base_fee_per_gas()? { - let suggested_priority_fee_per_gas = U256::from(1_000_000_000u64); - Ok(next_block_base_fee_per_gas + suggested_priority_fee_per_gas) - } else { - // We return a hardcoded value for networks without EIP-1559 - Ok(U256::from(8_000_000_000u64)) + if let Some(next_block_base_fee) = self.next_block_base_fee_per_gas()? { + if let Some(max_fee_per_gas) = transaction.max_fee_per_gas() { + if *max_fee_per_gas < next_block_base_fee { + return Err(ProviderError::AutoMineMaxFeePerGasTooLow { + expected: next_block_base_fee, + actual: *max_fee_per_gas, + }); + } + } else { + let gas_price = transaction.gas_price(); + if *gas_price < next_block_base_fee { + return Err(ProviderError::AutoMineGasPriceTooLow { + expected: next_block_base_fee, + actual: *gas_price, + }); + } + } } + + Ok(()) } +} - pub fn nonce( - &mut self, - address: &Address, - block_spec: Option<&BlockSpec>, - state_overrides: &StateOverrides, - ) -> Result { - state_overrides - .account_override(address) - .and_then(|account_override| account_override.nonce) - .map_or_else( - || { - if matches!(block_spec, Some(BlockSpec::Tag(BlockTag::Pending))) { - self.account_next_nonce(address) - } else { - self.execute_in_block_context( - block_spec, - move |_blockchain, _block, state| { - let nonce = - state.basic(*address)?.map_or(0, |account| account.nonce); +impl ProviderData +where + ChainSpecT: SyncProviderSpec, - Ok(nonce) - }, - )? - } - }, - Ok, - ) + TimerT: Clone + TimeSinceEpoch, +{ + /// Returns the chain ID. + pub fn chain_id(&self) -> u64 { + self.blockchain.chain_id() } - pub fn pending_transactions(&self) -> impl Iterator { - self.mem_pool.transactions() + /// Returns the local EVM's [`SpecId`]. + pub fn evm_spec_id(&self) -> SpecId { + self.hardfork().into() } - pub fn remove_filter(&mut self, filter_id: &U256) -> bool { - self.remove_filter_impl::(filter_id) + /// Returns the local hardfork. + pub fn hardfork(&self) -> ChainSpecT::Hardfork { + self.blockchain.spec_id() } - pub fn remove_subscription(&mut self, filter_id: &U256) -> bool { - self.remove_filter_impl::(filter_id) + /// Returns the last block in the blockchain. + pub fn last_block( + &self, + ) -> Result< + Arc>>, + BlockchainError, + > { + self.blockchain.last_block() } - /// Removes the transaction with the provided hash from the mem pool, if it - /// exists. - pub fn remove_pending_transaction( - &mut self, - transaction_hash: &B256, - ) -> Option> { - self.mem_pool.remove_transaction(transaction_hash) + /// Returns the number of the last block in the blockchain. + pub fn last_block_number(&self) -> u64 { + self.blockchain.last_block_number() } - pub fn revert_to_snapshot(&mut self, snapshot_id: u64) -> bool { - // Ensure that, if the snapshot exists, we also remove all subsequent snapshots, - // as they can only be used once in Ganache. - let mut removed_snapshots = self.snapshots.split_off(&snapshot_id); + /// Makes a snapshot of the instance's state and returns the snapshot ID. + pub fn make_snapshot(&mut self) -> u64 { + let id = self.next_snapshot_id; + self.next_snapshot_id += 1; + + let snapshot = Snapshot { + block_number: self.blockchain.last_block_number(), + block_number_to_state_id: self.block_number_to_state_id.clone(), + block_time_offset_seconds: self.block_time_offset_seconds, + coinbase: self.beneficiary, + irregular_state: self.irregular_state.clone(), + mem_pool: self.mem_pool.clone(), + next_block_base_fee_per_gas: self.next_block_base_fee_per_gas, + next_block_timestamp: self.next_block_timestamp, + parent_beacon_block_root_generator: self.parent_beacon_block_root_generator.clone(), + prev_randao_generator: self.prev_randao_generator.clone(), + time: Instant::now(), + }; + self.snapshots.insert(id, snapshot); + + id + } - if let Some(snapshot) = removed_snapshots.remove(&snapshot_id) { - let Snapshot { - block_number, - block_number_to_state_id, - block_time_offset_seconds, - coinbase, - irregular_state, - mem_pool, - next_block_base_fee_per_gas, - next_block_timestamp, - parent_beacon_block_root_generator, - prev_randao_generator, - time, - } = snapshot; + /// Calculates the next block's base fee per gas. + pub fn next_block_base_fee_per_gas(&self) -> Result, BlockchainError> { + if self.evm_spec_id() < SpecId::LONDON { + return Ok(None); + } - self.block_number_to_state_id = block_number_to_state_id; + self.next_block_base_fee_per_gas + .map_or_else( + || { + let last_block = self.last_block()?; - // We compute a new offset such that: - // now + new_offset == snapshot_date + old_offset - let duration_since_snapshot = Instant::now().duration_since(time); - self.block_time_offset_seconds = block_time_offset_seconds - + i64::try_from(duration_since_snapshot.as_secs()).expect("duration too large"); + Ok(calculate_next_base_fee_per_gas::( + self.blockchain.spec_id(), + last_block.header(), + )) + }, + Ok, + ) + .map(Some) + } - self.beneficiary = coinbase; - self.blockchain - .revert_to_block(block_number) - .expect("Snapshotted block should exist"); + /// Calculates the next block's base fee per blob gas. + pub fn next_block_base_fee_per_blob_gas( + &self, + ) -> Result, BlockchainError> { + if self.evm_spec_id() < SpecId::CANCUN { + return Ok(None); + } - self.irregular_state = irregular_state; - self.mem_pool = mem_pool; - self.next_block_base_fee_per_gas = next_block_base_fee_per_gas; - self.next_block_timestamp = next_block_timestamp; - self.parent_beacon_block_root_generator = parent_beacon_block_root_generator; - self.prev_randao_generator = prev_randao_generator; + let last_block = self.last_block()?; + let base_fee = calculate_next_base_fee_per_blob_gas(last_block.header()); - true + Ok(Some(U256::from(base_fee))) + } + + /// Calculates the gas price for the next block. + pub fn next_gas_price(&self) -> Result> { + if let Some(next_block_base_fee_per_gas) = self.next_block_base_fee_per_gas()? { + let suggested_priority_fee_per_gas = U256::from(1_000_000_000u64); + Ok(next_block_base_fee_per_gas + suggested_priority_fee_per_gas) } else { - false + // We return a hardcoded value for networks without EIP-1559 + Ok(U256::from(8_000_000_000u64)) } } +} - pub fn run_call( +impl ProviderData +where + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + + TimerT: Clone + TimeSinceEpoch, +{ + /// Returns the balance of the account corresponding to the provided address + /// at the optionally specified [`BlockSpec`]. Otherwise uses the last + /// block. + pub fn balance( &mut self, - transaction: transaction::Signed, + address: Address, + block_spec: Option<&BlockSpec>, + ) -> Result> { + self.execute_in_block_context::>>( + block_spec, + move |_blockchain, _block, state| { + Ok(state + .basic(address)? + .map_or(U256::ZERO, |account| account.balance)) + }, + )? + } + + pub fn debug_trace_call( + &mut self, + transaction: ChainSpecT::Transaction, block_spec: &BlockSpec, - state_overrides: &StateOverrides, - ) -> Result { + trace_config: DebugTraceConfig, + ) -> Result, ProviderError> { let cfg_env = self.create_evm_config(Some(block_spec))?; - let mut debugger = Debugger::with_mocker( - Mocker::new(self.call_override.clone()), - self.verbose_tracing, - ); - + let mut tracer = Eip3155AndRawTracers::new(trace_config, self.verbose_tracing); let precompiles = self.custom_precompiles.clone(); + self.execute_in_block_context(Some(block_spec), |blockchain, block, state| { - let execution_result = call::run_call(RunCallArgs { + let result = run_call(RunCallArgs { blockchain, header: block.header(), state, - state_overrides, - cfg_env, + state_overrides: &StateOverrides::default(), + cfg_env: cfg_env.clone(), transaction, precompiles: &precompiles, debug_context: Some(DebugContext { - data: &mut debugger, - register_handles_fn: register_debugger_handles, + data: &mut tracer, + register_handles_fn: register_eip_3155_and_raw_tracers_handles, }), })?; - let Debugger { - console_logger, - trace_collector, - .. - } = debugger; - - let mut traces = trace_collector.into_traces(); - // Should only have a single raw trace - assert_eq!(traces.len(), 1); - - Ok(CallResult { - console_log_inputs: console_logger.into_encoded_messages(), - execution_result, - trace: traces.pop().expect("Must have a trace"), - }) + Ok(execution_result_to_debug_result(result, tracer)) })? } - pub fn transaction_receipt( - &self, - transaction_hash: &B256, - ) -> Result>>, ProviderError> { - self.blockchain - .receipt_by_transaction_hash(transaction_hash) - .map_err(ProviderError::Blockchain) - } - - pub fn set_min_gas_price(&mut self, min_gas_price: U256) -> Result<(), ProviderError> { - if self.spec_id() >= SpecId::LONDON { - return Err(ProviderError::SetMinGasPriceUnsupported); - } - - self.min_gas_price = min_gas_price; - - Ok(()) - } - - pub fn set_verbose_tracing(&mut self, verbose_tracing: bool) { - self.verbose_tracing = verbose_tracing; - } - - pub fn send_transaction( + // Matches Hardhat implementation + pub fn fee_history( &mut self, - transaction: transaction::Signed, - ) -> Result { - if transaction.transaction_type() == transaction::Type::Eip4844 { - if !self.is_auto_mining || mempool::has_transactions(&self.mem_pool) { - return Err(ProviderError::BlobMemPoolUnsupported); - } - - let transaction_hash = *transaction.transaction_hash(); - - // Despite not adding the transaction to the mempool, we still notify - // subscribers - self.notify_subscribers_about_pending_transaction(&transaction_hash); - - let result = self.mine_and_commit_block_impl( - move |provider, config, options, debugger| { - provider.mine_block_with_single_transaction( - config, - options, - transaction, - debugger, - ) - }, - BlockOptions::default(), - )?; - - return Ok(SendTransactionResult { - transaction_hash, - mining_results: vec![result], + block_count: u64, + newest_block_spec: &BlockSpec, + percentiles: Option>, + ) -> Result> { + if self.evm_spec_id() < SpecId::LONDON { + return Err(ProviderError::UnmetHardfork { + actual: self.evm_spec_id(), + minimum: SpecId::LONDON, }); } - let snapshot_id = if self.is_auto_mining { - self.validate_auto_mine_transaction(&transaction)?; + let latest_block_number = self.last_block_number(); + let pending_block_number = latest_block_number + 1; + let newest_block_number = self + .block_by_block_spec(newest_block_spec)? + // None if pending block + .map_or(pending_block_number, |block| block.header().number); + let oldest_block_number = if newest_block_number < block_count { + 0 + } else { + newest_block_number - block_count + 1 + }; + let last_block_number = newest_block_number + 1; - Some(self.make_snapshot()) + let pending_block = if last_block_number >= pending_block_number { + let DebugMineBlockResultAndState { block, .. } = self.mine_pending_block()?; + Some(block) } else { None }; - let transaction_hash = self.add_pending_transaction(transaction).map_err(|error| { - if let Some(snapshot_id) = snapshot_id { - self.revert_to_snapshot(snapshot_id); - } - - error - })?; - - let mut mining_results = Vec::new(); - snapshot_id - .map(|snapshot_id| -> Result<(), ProviderError> { - loop { - let result = self - .mine_and_commit_block(BlockOptions::default()) - .map_err(|error| { - self.revert_to_snapshot(snapshot_id); - - error - })?; - - let mined_transaction = result.has_transaction(&transaction_hash); - - mining_results.push(result); - - if mined_transaction { - break; - } - } - - while self.mem_pool.has_pending_transactions() { - let result = self - .mine_and_commit_block(BlockOptions::default()) - .map_err(|error| { - self.revert_to_snapshot(snapshot_id); - - error - })?; - - mining_results.push(result); - } - - self.snapshots.remove(&snapshot_id); - - Ok(()) - }) - .transpose()?; - - Ok(SendTransactionResult { - transaction_hash, - mining_results, - }) - } - - /// Sets whether the miner should mine automatically. - pub fn set_auto_mining(&mut self, enabled: bool) { - self.is_auto_mining = enabled; - } - - pub fn set_balance(&mut self, address: Address, balance: U256) -> Result<(), ProviderError> { - let mut modified_state = (*self.current_state()?).clone(); - let account_info = modified_state.modify_account( - address, - AccountModifierFn::new(Box::new(move |account_balance, _, _| { - *account_balance = balance; - })), - )?; + let mut result = FeeHistoryResult::new(oldest_block_number); - let state_root = modified_state.state_root()?; + let mut reward_and_percentile = percentiles.and_then(|percentiles| { + if percentiles.is_empty() { + None + } else { + Some((Vec::default(), percentiles)) + } + }); - self.mem_pool.update(&modified_state)?; + let range_includes_remote_blocks = self.fork_metadata.as_ref().map_or(false, |metadata| { + oldest_block_number <= metadata.fork_block_number + }); - let block_number = self.blockchain.last_block_number(); - self.irregular_state - .state_override_at_block_number(block_number) - .or_insert_with(|| StateOverride::with_state_root(state_root)) - .diff - .apply_account_change(address, account_info.clone()); + if range_includes_remote_blocks { + let last_remote_block = cmp::min( + self.fork_metadata + .as_ref() + .expect("we checked that there is a fork") + .fork_block_number, + last_block_number, + ); + let remote_block_count = last_remote_block - oldest_block_number + 1; - self.add_state_to_cache(modified_state, block_number); + let rpc_client = self + .rpc_client + .as_ref() + .expect("we checked that there is a fork"); + let FeeHistoryResult { + oldest_block: _, + base_fee_per_gas, + gas_used_ratio, + reward: remote_reward, + } = tokio::task::block_in_place(|| { + self.runtime_handle.block_on( + rpc_client.fee_history( + remote_block_count, + newest_block_spec.clone(), + reward_and_percentile + .as_ref() + .map(|(_, percentiles)| percentiles.clone()), + ), + ) + })?; - Ok(()) - } + result.base_fee_per_gas = base_fee_per_gas; + result.gas_used_ratio = gas_used_ratio; + if let Some((ref mut reward, _)) = reward_and_percentile.as_mut() { + if let Some(remote_reward) = remote_reward { + *reward = remote_reward; + } + } + } - /// Sets the gas limit used for mining new blocks. - pub fn set_block_gas_limit(&mut self, gas_limit: NonZeroU64) -> Result<(), ProviderError> { - let state = self.current_state()?; - self.mem_pool - .set_block_gas_limit(&*state, gas_limit) - .map_err(ProviderError::State) - } + let first_local_block = if range_includes_remote_blocks { + cmp::min( + self.fork_metadata + .as_ref() + .expect("we checked that there is a fork") + .fork_block_number, + last_block_number, + ) + 1 + } else { + oldest_block_number + }; - pub fn set_code(&mut self, address: Address, code: Bytes) -> Result<(), ProviderError> { - let code = Bytecode::new_raw(code.clone()); - let irregular_code = code.clone(); + for block_number in first_local_block..=last_block_number { + if block_number < pending_block_number { + let block = self + .blockchain + .block_by_number(block_number)? + .expect("Block must exist as i is at most the last block number"); - // We clone to automatically revert in case of subsequent errors. - let mut modified_state = (*self.current_state()?).clone(); - let mut account_info = modified_state.modify_account( - address, - AccountModifierFn::new(Box::new(move |_, _, account_code| { - *account_code = Some(code.clone()); - })), - )?; + let header = block.header(); + result + .base_fee_per_gas + .push(header.base_fee_per_gas.unwrap_or(U256::ZERO)); - // The code was stripped from the account, so we need to re-add it for the - // irregular state. - account_info.code = Some(irregular_code.clone()); + if block_number < last_block_number { + result + .gas_used_ratio + .push(gas_used_ratio(header.gas_used, header.gas_limit)); - let state_root = modified_state.state_root()?; + if let Some((ref mut reward, percentiles)) = reward_and_percentile.as_mut() { + reward.push(compute_rewards(&block, percentiles)?); + } + } + } else if block_number == pending_block_number { + let next_block_base_fee_per_gas = self + .next_block_base_fee_per_gas()? + .expect("We checked that EIP-1559 is active"); + result.base_fee_per_gas.push(next_block_base_fee_per_gas); - let block_number = self.blockchain.last_block_number(); - self.irregular_state - .state_override_at_block_number(block_number) - .or_insert_with(|| StateOverride::with_state_root(state_root)) - .diff - .apply_account_change(address, account_info.clone()); + if block_number < last_block_number { + let block = pending_block.as_ref().expect("We mined the pending block"); + let header = block.header(); + result + .gas_used_ratio + .push(gas_used_ratio(header.gas_used, header.gas_limit)); - self.add_state_to_cache(modified_state, block_number); + if let Some((ref mut reward, percentiles)) = reward_and_percentile.as_mut() { + // We don't compute this for the pending block, as there's no + // effective miner fee yet. + reward.push(percentiles.iter().map(|_| U256::ZERO).collect()); + } + } + } else if block_number == pending_block_number + 1 { + let block = pending_block.as_ref().expect("We mined the pending block"); + result + .base_fee_per_gas + .push(calculate_next_base_fee_per_gas::( + self.blockchain.spec_id(), + block.header(), + )); + } + } - Ok(()) - } + if let Some((reward, _)) = reward_and_percentile { + result.reward = Some(reward); + } - /// Sets the coinbase. - pub fn set_coinbase(&mut self, coinbase: Address) { - self.beneficiary = coinbase; + Ok(result) } - /// Sets the next block's base fee per gas. - pub fn set_next_block_base_fee_per_gas( + pub fn get_code( &mut self, - base_fee_per_gas: U256, - ) -> Result<(), ProviderError> { - let spec_id = self.spec_id(); - if spec_id < SpecId::LONDON { - return Err(ProviderError::SetNextBlockBaseFeePerGasUnsupported { spec_id }); - } + address: Address, + block_spec: Option<&BlockSpec>, + ) -> Result> { + self.execute_in_block_context(block_spec, move |_blockchain, _block, state| { + let code = state + .basic(address)? + .map_or(Ok(Bytes::new()), |account_info| { + state.code_by_hash(account_info.code_hash).map(|bytecode| { + // The `Bytecode` REVM struct pad the bytecode with 33 bytes of 0s for the + // `Checked` and `Analysed` variants. `Bytecode::original_bytes` returns + // unpadded version. + bytecode.original_bytes() + }) + })?; - self.next_block_base_fee_per_gas = Some(base_fee_per_gas); + Ok(code) + })? + } - Ok(()) + pub fn get_storage_at( + &mut self, + address: Address, + index: U256, + block_spec: Option<&BlockSpec>, + ) -> Result> { + self.execute_in_block_context::>>( + block_spec, + move |_blockchain, _block, state| Ok(state.storage(address, index)?), + )? } - /// Set the next block timestamp. - pub fn set_next_block_timestamp(&mut self, timestamp: u64) -> Result { - let latest_block = self.blockchain.last_block()?; - let latest_block_header = latest_block.header(); + pub fn get_transaction_count( + &mut self, + address: Address, + block_spec: Option<&BlockSpec>, + ) -> Result> { + self.execute_in_block_context::>>( + block_spec, + move |_blockchain, _block, state| { + let nonce = state + .basic(address)? + .map_or(0, |account_info| account_info.nonce); - match timestamp.cmp(&latest_block_header.timestamp) { - Ordering::Less => Err(ProviderError::TimestampLowerThanPrevious { - proposed: timestamp, - previous: latest_block_header.timestamp, - }), - Ordering::Equal if !self.allow_blocks_with_same_timestamp => { - Err(ProviderError::TimestampEqualsPrevious { - proposed: timestamp, - }) - } - Ordering::Equal | Ordering::Greater => { - self.next_block_timestamp = Some(timestamp); - Ok(timestamp) - } - } + Ok(nonce) + }, + )? } - /// Sets the next block's prevrandao. - pub fn set_next_prev_randao(&mut self, prev_randao: B256) -> Result<(), ProviderError> { - let spec_id = self.spec_id(); - if spec_id < SpecId::MERGE { - return Err(ProviderError::SetNextPrevRandaoUnsupported { spec_id }); - } + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] + pub fn interval_mine(&mut self) -> Result> { + let result = self.mine_and_commit_block(BlockOptions::default())?; - self.prev_randao_generator.set_next(prev_randao); + self.logger + .log_interval_mined(self.evm_spec_id(), &result) + .map_err(ProviderError::Logger)?; - Ok(()) + Ok(true) } - pub fn set_nonce(&mut self, address: Address, nonce: u64) -> Result<(), ProviderError> { - if mempool::has_transactions(&self.mem_pool) { - return Err(ProviderError::SetAccountNonceWithPendingTransactions); - } + /// Mines a block with the provided options, using transactions in the + /// mempool, and commits it to the blockchain. + pub fn mine_and_commit_block( + &mut self, + options: BlockOptions, + ) -> Result< + DebugMineBlockResult>, + ProviderError, + > { + self.mine_and_commit_block_impl(Self::mine_block_with_mem_pool, options) + } - let previous_nonce = self - .current_state()? - .basic(address)? - .map_or(0, |account| account.nonce); + /// Mines `number_of_blocks` blocks with the provided `interval` between + /// them. + pub fn mine_and_commit_blocks( + &mut self, + number_of_blocks: u64, + interval: u64, + ) -> Result< + Vec>>, + ProviderError, + > { + // There should be at least 2 blocks left for the reservation to work, + // because we always mine a block after it. But here we use a bigger + // number to err on the side of safety. + const MINIMUM_RESERVABLE_BLOCKS: u64 = 6; - if nonce < previous_nonce { - return Err(ProviderError::SetAccountNonceLowerThanCurrent { - previous: previous_nonce, - proposed: nonce, - }); + if number_of_blocks == 0 { + return Ok(Vec::new()); } - // We clone to automatically revert in case of subsequent errors. - let mut modified_state = (*self.current_state()?).clone(); - let account_info = modified_state.modify_account( - address, - AccountModifierFn::new(Box::new(move |_, account_nonce, _| *account_nonce = nonce)), - )?; + let mine_block_with_interval = |data: &mut ProviderData, + mined_blocks: &mut Vec< + DebugMineBlockResult>, + >| + -> Result<(), ProviderError> { + let previous_timestamp = mined_blocks + .last() + .expect("at least one block was mined") + .block + .header() + .timestamp; - let state_root = modified_state.state_root()?; + let options = BlockOptions { + timestamp: Some(previous_timestamp + interval), + ..BlockOptions::default() + }; - self.mem_pool.update(&modified_state)?; + let mined_block = data.mine_and_commit_block(options)?; + mined_blocks.push(mined_block); - let block_number = self.last_block_number(); - self.irregular_state - .state_override_at_block_number(block_number) - .or_insert_with(|| StateOverride::with_state_root(state_root)) - .diff - .apply_account_change(address, account_info.clone()); + Ok(()) + }; - self.add_state_to_cache(modified_state, block_number); + // Limit the pre-allocated capacity based on the minimum reservable number of + // blocks to avoid too large allocations. + let mut mined_blocks = Vec::with_capacity( + usize::try_from(number_of_blocks.min(2 * MINIMUM_RESERVABLE_BLOCKS)) + .expect("number of blocks exceeds {u64::MAX}"), + ); - Ok(()) - } + // we always mine the first block, and we don't apply the interval for it + mined_blocks.push(self.mine_and_commit_block(BlockOptions::default())?); - pub fn set_account_storage_slot( - &mut self, - address: Address, - index: U256, - value: U256, - ) -> Result<(), ProviderError> { - // We clone to automatically revert in case of subsequent errors. - let mut modified_state = (*self.current_state()?).clone(); - let old_value = modified_state.set_account_storage_slot(address, index, value)?; + while u64::try_from(mined_blocks.len()).expect("usize cannot be larger than u128") + < number_of_blocks + && self.mem_pool.has_pending_transactions() + { + mine_block_with_interval(self, &mut mined_blocks)?; + } - let slot = EvmStorageSlot::new_changed(old_value, value); - let account_info = modified_state.basic(address).and_then(|mut account_info| { - // Retrieve the code if it's not empty. This is needed for the irregular state. - if let Some(account_info) = &mut account_info { - if account_info.code_hash != KECCAK_EMPTY { - account_info.code = Some(modified_state.code_by_hash(account_info.code_hash)?); - } - } + // If there is at least one remaining block, we mine one. This way, we + // guarantee that there's an empty block immediately before and after the + // reservation. This makes the logging easier to get right. + if u64::try_from(mined_blocks.len()).expect("usize cannot be larger than u128") + < number_of_blocks + { + mine_block_with_interval(self, &mut mined_blocks)?; + } - Ok(account_info) - })?; + let remaining_blocks = number_of_blocks + - u64::try_from(mined_blocks.len()).expect("usize cannot be larger than u128"); - let state_root = modified_state.state_root()?; + if remaining_blocks < MINIMUM_RESERVABLE_BLOCKS { + for _ in 0..remaining_blocks { + mine_block_with_interval(self, &mut mined_blocks)?; + } + } else { + let current_state = (*self.current_state()?).clone(); - let block_number = self.blockchain.last_block_number(); - self.irregular_state - .state_override_at_block_number(block_number) - .or_insert_with(|| StateOverride::with_state_root(state_root)) - .diff - .apply_storage_change(address, index, slot, account_info); + self.blockchain + .reserve_blocks(remaining_blocks - 1, interval)?; - self.add_state_to_cache(modified_state, block_number); + // Ensure there is a cache entry for the last reserved block, to avoid + // recomputation + self.add_state_to_cache(current_state, self.last_block_number()); - Ok(()) - } + let previous_timestamp = self.blockchain.last_block()?.header().timestamp; + let options = BlockOptions { + timestamp: Some(previous_timestamp + interval), + ..BlockOptions::default() + }; - pub fn sign( - &self, - address: &Address, - message: Bytes, - ) -> Result { - match self.local_accounts.get(address) { - Some(secret_key) => Ok(signature::SignatureWithRecoveryId::new( - &message[..], - secret_key, - )?), - None => Err(ProviderError::UnknownAddress { address: *address }), + let mined_block = self.mine_and_commit_block(options)?; + mined_blocks.push(mined_block); } - } - pub fn sign_typed_data_v4( - &self, - address: &Address, - message: &TypedData, - ) -> Result { - match self.local_accounts.get(address) { - Some(secret_key) => { - let hash = message.eip712_signing_hash()?; - Ok(signature::SignatureWithRecoveryId::new( - RecoveryMessage::Hash(hash), - secret_key, - )?) - } - None => Err(ProviderError::UnknownAddress { address: *address }), - } - } + mined_blocks.shrink_to_fit(); - pub fn spec_id(&self) -> SpecId { - self.blockchain.spec_id() + Ok(mined_blocks) } - pub fn stop_impersonating_account(&mut self, address: Address) -> bool { - self.impersonated_accounts.remove(&address) - } + /// Mines a pending block, without modifying any values. + pub fn mine_pending_block( + &mut self, + ) -> Result, ProviderError> + { + let (block_timestamp, _new_offset) = self.next_block_timestamp(None)?; - pub fn total_difficulty_by_hash(&self, hash: &B256) -> Result, ProviderError> { - self.blockchain - .total_difficulty_by_hash(hash) - .map_err(ProviderError::Blockchain) + // Mining a pending block shouldn't affect the mix hash. + self.mine_block( + Self::mine_block_with_mem_pool, + BlockOptions { + timestamp: Some(block_timestamp), + ..BlockOptions::default() + }, + ) } - /// Get a transaction by hash from the blockchain or from the mempool if - /// it's not mined yet. - pub fn transaction_by_hash( - &self, - hash: &B256, - ) -> Result, ProviderError> { - let transaction = if let Some(tx) = self.mem_pool.transaction_by_hash(hash) { - Some(TransactionAndBlock { - transaction: tx.pending().clone(), - block_data: None, - is_pending: true, - }) - } else if let Some(block) = self.blockchain.block_by_transaction_hash(hash)? { - let tx_index_u64 = self - .blockchain - .receipt_by_transaction_hash(hash)? - .expect("If the transaction was inserted in a block, it must have a receipt") - .transaction_index; - let tx_index = - usize::try_from(tx_index_u64).expect("Indices cannot be larger than usize::MAX"); - - let transaction = block - .transactions() - .get(tx_index) - .expect("Transaction index must be valid, since it's from the receipt.") - .clone(); - - Some(TransactionAndBlock { - transaction, - block_data: Some(BlockDataForTransaction { - block, - transaction_index: tx_index_u64, - }), - is_pending: false, - }) - } else { - None - }; + pub fn nonce( + &mut self, + address: &Address, + block_spec: Option<&BlockSpec>, + state_overrides: &StateOverrides, + ) -> Result> { + state_overrides + .account_override(address) + .and_then(|account_override| account_override.nonce) + .map_or_else( + || { + if matches!(block_spec, Some(BlockSpec::Tag(BlockTag::Pending))) { + self.account_next_nonce(address) + } else { + self.execute_in_block_context( + block_spec, + move |_blockchain, _block, state| { + let nonce = + state.basic(*address)?.map_or(0, |account| account.nonce); - Ok(transaction) + Ok(nonce) + }, + )? + } + }, + Ok, + ) } - fn add_pending_transaction( + pub fn run_call( &mut self, - transaction: transaction::Signed, - ) -> Result { - let transaction_hash = *transaction.transaction_hash(); - - let state = self.current_state()?; - // Handles validation - self.mem_pool.add_transaction(&*state, transaction)?; - - self.notify_subscribers_about_pending_transaction(&transaction_hash); + transaction: ChainSpecT::Transaction, + block_spec: &BlockSpec, + state_overrides: &StateOverrides, + ) -> Result, ProviderError> { + let cfg_env = self.create_evm_config(Some(block_spec))?; - Ok(transaction_hash) - } + let mut debugger = Debugger::with_mocker( + Mocker::new(self.call_override.clone()), + self.verbose_tracing, + ); - /// Creates a configuration, taking into the hardfork at the provided - /// `BlockSpec`. If none is provided, assumes the hardfork for newly - /// mined blocks. - fn create_evm_config( - &self, - block_spec: Option<&BlockSpec>, - ) -> Result, ProviderError> { - let block_number = block_spec - .map(|block_spec| self.block_number_by_block_spec(block_spec)) - .transpose()? - .flatten(); + let precompiles = self.custom_precompiles.clone(); + self.execute_in_block_context(Some(block_spec), |blockchain, block, state| { + let execution_result = call::run_call(RunCallArgs { + blockchain, + header: block.header(), + state, + state_overrides, + cfg_env, + transaction, + precompiles: &precompiles, + debug_context: Some(DebugContext { + data: &mut debugger, + register_handles_fn: register_debugger_handles, + }), + })?; - let spec_id = if let Some(block_number) = block_number { - self.blockchain.spec_at_block_number(block_number)? - } else { - self.blockchain.spec_id() - }; + let Debugger { + console_logger, + trace_collector, + .. + } = debugger; - let mut cfg_env = CfgEnv::default(); - cfg_env.chain_id = self.blockchain.chain_id(); - cfg_env.limit_contract_code_size = if self.allow_unlimited_contract_size { - Some(usize::MAX) - } else { - None - }; - cfg_env.disable_eip3607 = true; + let mut traces = trace_collector.into_traces(); + // Should only have a single raw trace + assert_eq!(traces.len(), 1); - Ok(CfgEnvWithEvmWiring::::new(cfg_env, spec_id)) + Ok(CallResult { + console_log_inputs: console_logger.into_encoded_messages(), + execution_result, + trace: traces.pop().expect("Must have a trace"), + }) + })? } fn execute_in_block_context( &mut self, block_spec: Option<&BlockSpec>, function: impl FnOnce( - &dyn SyncBlockchain, StateError>, - &Arc>>, + &dyn SyncBlockchain, StateError>, + &Arc>>, &Box>, ) -> T, - ) -> Result { + ) -> Result> { let block = if let Some(block_spec) = block_spec { self.block_by_block_spec(block_spec)? } else { @@ -1961,63 +2118,12 @@ impl ProviderData { } } - /// Mine a block using the provided options. If an option has not been - /// specified, it will be set using the provider's configuration values. - fn mine_block( - &mut self, - mine_fn: impl FnOnce( - &mut ProviderData, - &CfgEnvWithEvmWiring, - BlockOptions, - &mut Debugger, - ) - -> Result, ProviderError>, - mut options: BlockOptions, - ) -> Result, ProviderError> { - options.base_fee = options.base_fee.or(self.next_block_base_fee_per_gas); - options.beneficiary = Some(options.beneficiary.unwrap_or(self.beneficiary)); - options.gas_limit = Some(options.gas_limit.unwrap_or_else(|| self.block_gas_limit())); - - let evm_config = self.create_evm_config(None)?; - - if options.mix_hash.is_none() && evm_config.spec_id >= SpecId::MERGE { - options.mix_hash = Some(self.prev_randao_generator.next_value()); - } - - if evm_config.spec_id >= SpecId::CANCUN { - options.parent_beacon_block_root = options - .parent_beacon_block_root - .or_else(|| Some(self.parent_beacon_block_root_generator.next_value())); - } - - let mut debugger = Debugger::with_mocker( - Mocker::new(self.call_override.clone()), - self.verbose_tracing, - ); - - let result = mine_fn(self, &evm_config, options, &mut debugger)?; - - let Debugger { - console_logger, - trace_collector, - .. - } = debugger; - - let traces = trace_collector.into_traces(); - - Ok(DebugMineBlockResultAndState::new( - result, - traces, - console_logger.into_encoded_messages(), - )) - } - fn mine_block_with_mem_pool( &mut self, - config: &CfgEnvWithEvmWiring, + config: &CfgEnvWithEvmWiring, options: BlockOptions, - debugger: &mut Debugger, - ) -> Result, ProviderError> { + debugger: &mut Debugger, + ) -> Result, ProviderError> { let state_to_be_modified = (*self.current_state()?).clone(); let result = mine_block( self.blockchain.as_ref(), @@ -2027,7 +2133,7 @@ impl ProviderData { options, self.min_gas_price, self.initial_config.mining.mem_pool.order, - miner_reward(config.spec_id).unwrap_or(U256::ZERO), + miner_reward(config.spec_id.into()).unwrap_or(U256::ZERO), Some(DebugContext { data: debugger, register_handles_fn: register_debugger_handles, @@ -2037,13 +2143,14 @@ impl ProviderData { Ok(result) } + /// Mines a block with the provided transaction. fn mine_block_with_single_transaction( &mut self, - config: &CfgEnvWithEvmWiring, + config: &CfgEnvWithEvmWiring, options: BlockOptions, - transaction: transaction::Signed, - debugger: &mut Debugger, - ) -> Result, ProviderError> { + transaction: ChainSpecT::Transaction, + debugger: &mut Debugger, + ) -> Result, ProviderError> { let state_to_be_modified = (*self.current_state()?).clone(); let result = mine_block_with_single_transaction( self.blockchain.as_ref(), @@ -2052,7 +2159,7 @@ impl ProviderData { config, options, self.min_gas_price, - miner_reward(config.spec_id).unwrap_or(U256::ZERO), + miner_reward(config.spec_id.into()).unwrap_or(U256::ZERO), Some(DebugContext { data: debugger, register_handles_fn: register_debugger_handles, @@ -2061,277 +2168,305 @@ impl ProviderData { Ok(result) } +} - /// Mines a pending block, without modifying any values. - pub fn mine_pending_block( +impl ProviderData +where + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionType + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +{ + pub fn send_transaction( &mut self, - ) -> Result, ProviderError> { - let (block_timestamp, _new_offset) = self.next_block_timestamp(None)?; - - // Mining a pending block shouldn't affect the mix hash. - self.mine_block( - Self::mine_block_with_mem_pool, - BlockOptions { - timestamp: Some(block_timestamp), - ..BlockOptions::default() - }, - ) - } - - pub fn mining_config(&self) -> &MiningConfig { - &self.initial_config.mining - } + transaction: ChainSpecT::Transaction, + ) -> Result, ProviderError> { + if transaction.transaction_type().is_eip4844() { + if !self.is_auto_mining || mempool::has_transactions(&self.mem_pool) { + return Err(ProviderError::BlobMemPoolUnsupported); + } - /// Get the timestamp for the next block. - /// Ported from - fn next_block_timestamp( - &self, - timestamp: Option, - ) -> Result<(u64, Option), ProviderError> { - let latest_block = self.blockchain.last_block()?; - let latest_block_header = latest_block.header(); + let transaction_hash = *transaction.transaction_hash(); - let current_timestamp = - i64::try_from(self.timer.since_epoch()).expect("timestamp too large"); + // Despite not adding the transaction to the mempool, we still notify + // subscribers + self.notify_subscribers_about_pending_transaction(&transaction_hash); - let (mut block_timestamp, mut new_offset) = if let Some(timestamp) = timestamp { - timestamp.checked_sub(latest_block_header.timestamp).ok_or( - ProviderError::TimestampLowerThanPrevious { - proposed: timestamp, - previous: latest_block_header.timestamp, + let result = self.mine_and_commit_block_impl( + move |provider, config, options, debugger| { + provider.mine_block_with_single_transaction( + config, + options, + transaction, + debugger, + ) }, + BlockOptions::default(), )?; - let offset = i64::try_from(timestamp).expect("timestamp too large") - current_timestamp; - (timestamp, Some(offset)) - } else if let Some(next_block_timestamp) = self.next_block_timestamp { - let offset = i64::try_from(next_block_timestamp).expect("timestamp too large") - - current_timestamp; + return Ok(SendTransactionResult { + transaction_hash, + mining_results: vec![result], + }); + } - (next_block_timestamp, Some(offset)) - } else { - let next_timestamp = u64::try_from(current_timestamp + self.block_time_offset_seconds) - .expect("timestamp must be positive"); + let snapshot_id = if self.is_auto_mining { + self.validate_auto_mine_transaction(&transaction)?; - (next_timestamp, None) + Some(self.make_snapshot()) + } else { + None }; - let timestamp_needs_increase = block_timestamp == latest_block_header.timestamp - && !self.allow_blocks_with_same_timestamp; - if timestamp_needs_increase { - block_timestamp += 1; - if new_offset.is_none() { - new_offset = Some(self.block_time_offset_seconds + 1); + let transaction_hash = self.add_pending_transaction(transaction).map_err(|error| { + if let Some(snapshot_id) = snapshot_id { + self.revert_to_snapshot(snapshot_id); } - } - Ok((block_timestamp, new_offset)) - } + error + })?; - fn next_filter_id(&mut self) -> U256 { - self.last_filter_id = self - .last_filter_id - .checked_add(U256::from(1)) - .expect("filter id starts at zero, so it'll never overflow for U256"); - self.last_filter_id - } + let mut mining_results = Vec::new(); + snapshot_id + .map(|snapshot_id| -> Result<(), ProviderError> { + loop { + let result = self + .mine_and_commit_block(BlockOptions::default()) + .map_err(|error| { + self.revert_to_snapshot(snapshot_id); - /// Notifies subscribers to `FilterData::NewPendingTransactions` about the - /// pending transaction with the provided hash. - fn notify_subscribers_about_pending_transaction(&mut self, transaction_hash: &B256) { - for (filter_id, filter) in self.filters.iter_mut() { - if let FilterData::NewPendingTransactions(events) = &mut filter.data { - if filter.is_subscription { - (self.subscriber_callback)(SubscriptionEvent { - filter_id: *filter_id, - result: SubscriptionEventData::NewPendingTransactions(*transaction_hash), - }); - } else { - events.push(*transaction_hash); - } - } - } - } + error + })?; - /// Notifies subscribers to `FilterData::Logs` and `FilterData::NewHeads` - /// about the mined block. - fn notify_subscribers_about_mined_block( - &mut self, - block_and_total_difficulty: &BlockAndTotalDifficulty< - L1ChainSpec, - BlockchainError, - >, - ) -> Result<(), BlockchainError> { - let block = &block_and_total_difficulty.block; - for (filter_id, filter) in self.filters.iter_mut() { - match &mut filter.data { - FilterData::Logs { criteria, logs } => { - let bloom = &block.header().logs_bloom; - if bloom_contains_log_filter(bloom, criteria) { - let receipts = block.transaction_receipts()?; - let new_logs = receipts.iter().flat_map(|receipt| receipt.logs()); + let mined_transaction = result.has_transaction(&transaction_hash); - let mut filtered_logs = filter_logs(new_logs, criteria); - if filter.is_subscription { - (self.subscriber_callback)(SubscriptionEvent { - filter_id: *filter_id, - result: SubscriptionEventData::Logs(filtered_logs.clone()), - }); - } else { - logs.append(&mut filtered_logs); - } + mining_results.push(result); + + if mined_transaction { + break; } } - FilterData::NewHeads(block_hashes) => { - if filter.is_subscription { - (self.subscriber_callback)(SubscriptionEvent { - filter_id: *filter_id, - result: SubscriptionEventData::NewHeads( - block_and_total_difficulty.clone(), - ), - }); - } else { - block_hashes.push(*block.hash()); - } + + while self.mem_pool.has_pending_transactions() { + let result = self + .mine_and_commit_block(BlockOptions::default()) + .map_err(|error| { + self.revert_to_snapshot(snapshot_id); + + error + })?; + + mining_results.push(result); } - FilterData::NewPendingTransactions(_) => (), - } - } - // Remove outdated filters - self.filters.retain(|_, filter| !filter.has_expired()); + self.snapshots.remove(&snapshot_id); + + Ok(()) + }) + .transpose()?; + + Ok(SendTransactionResult { + transaction_hash, + mining_results, + }) + } +} + +impl ProviderData +where + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Clone + Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, - Ok(()) - } + TimerT: Clone + TimeSinceEpoch, +{ + #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))] + pub fn debug_trace_transaction( + &mut self, + transaction_hash: &B256, + trace_config: DebugTraceConfig, + ) -> Result, ProviderError> { + let block = self + .blockchain + .block_by_transaction_hash(transaction_hash)? + .ok_or_else(|| ProviderError::InvalidTransactionHash(*transaction_hash))?; - fn remove_filter_impl(&mut self, filter_id: &U256) -> bool { - if let Some(filter) = self.filters.get(filter_id) { - filter.is_subscription == IS_SUBSCRIPTION && self.filters.remove(filter_id).is_some() - } else { - false - } - } + let header = block.header(); + let block_spec = Some(BlockSpec::Number(header.number)); - pub fn sign_transaction_request( - &self, - transaction_request: TransactionRequestAndSender, - ) -> Result { - let TransactionRequestAndSender { request, sender } = transaction_request; + let cfg_env = self.create_evm_config(block_spec.as_ref())?; - if self.impersonated_accounts.contains(&sender) { - let signed_transaction = request.fake_sign(sender); - transaction::validate(signed_transaction, self.blockchain.spec_id()) - .map_err(ProviderError::TransactionCreationError) - } else { - let secret_key = self - .local_accounts - .get(&sender) - .ok_or(ProviderError::UnknownAddress { address: sender })?; + let transactions = block.transactions().to_vec(); - // SAFETY: We know the secret key belongs to the sender, as we retrieved it from - // `local_accounts`. - let signed_transaction = - unsafe { request.sign_for_sender_unchecked(secret_key, sender) }?; + let prev_block_number = block.header().number - 1; + let prev_block_spec = Some(BlockSpec::Number(prev_block_number)); + let verbose_tracing = self.verbose_tracing; - transaction::validate(signed_transaction, self.blockchain.spec_id()) - .map_err(ProviderError::TransactionCreationError) - } + self.execute_in_block_context( + prev_block_spec.as_ref(), + |blockchain, _prev_block, state| { + let block_env = ChainSpecT::Block::new_block_env(header, cfg_env.spec_id); + + debug_trace_transaction( + blockchain, + state.clone(), + cfg_env, + trace_config, + block_env, + transactions, + transaction_hash, + verbose_tracing, + ) + .map_err(ProviderError::DebugTrace) + }, + )? } +} - fn validate_auto_mine_transaction( +impl ProviderData +where + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionMut + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + + TimerT: Clone + TimeSinceEpoch, +{ + /// Estimate the gas cost of a transaction. Matches Hardhat behavior. + pub fn estimate_gas( &mut self, - transaction: &transaction::Signed, - ) -> Result<(), ProviderError> { - let next_nonce = { self.account_next_nonce(transaction.caller())? }; + transaction: ChainSpecT::Transaction, + block_spec: &BlockSpec, + ) -> Result, ProviderError> { + let cfg_env = self.create_evm_config(Some(block_spec))?; + // Minimum gas cost that is required for transaction to be included in + // a block + let minimum_cost = transaction::initial_cost(&transaction, self.evm_spec_id()); - match transaction.nonce().cmp(&next_nonce) { - Ordering::Less => { - return Err(ProviderError::AutoMineNonceTooLow { - expected: next_nonce, - actual: transaction.nonce(), - }) - } - Ordering::Equal => (), - Ordering::Greater => { - return Err(ProviderError::AutoMineNonceTooHigh { - expected: next_nonce, - actual: transaction.nonce(), - }) - } - } + let state_overrides = StateOverrides::default(); - let max_priority_fee_per_gas = transaction - .max_priority_fee_per_gas() - .unwrap_or_else(|| transaction.gas_price()); + let mut debugger = Debugger::with_mocker( + Mocker::new(self.call_override.clone()), + self.verbose_tracing, + ); - if *max_priority_fee_per_gas < self.min_gas_price { - return Err(ProviderError::AutoMinePriorityFeeTooLow { - expected: self.min_gas_price, - actual: *max_priority_fee_per_gas, - }); - } + let precompiles = self.custom_precompiles.clone(); + self.execute_in_block_context(Some(block_spec), |blockchain, block, state| { + let header = block.header(); - if let Some(next_block_base_fee) = self.next_block_base_fee_per_gas()? { - if let Some(max_fee_per_gas) = transaction.max_fee_per_gas() { - if max_fee_per_gas < next_block_base_fee { - return Err(ProviderError::AutoMineMaxFeePerGasTooLow { - expected: next_block_base_fee, - actual: max_fee_per_gas, - }); - } - } else { - let gas_price = transaction.gas_price(); - if *gas_price < next_block_base_fee { - return Err(ProviderError::AutoMineGasPriceTooLow { - expected: next_block_base_fee, - actual: *gas_price, - }); - } + // Measure the gas used by the transaction with optional limit from call request + // defaulting to block limit. Report errors from initial call as if from + // `eth_call`. + let result = call::run_call(RunCallArgs { + blockchain, + header, + state, + state_overrides: &state_overrides, + cfg_env: cfg_env.clone(), + transaction: transaction.clone(), + precompiles: &precompiles, + debug_context: Some(DebugContext { + data: &mut debugger, + register_handles_fn: register_debugger_handles, + }), + })?; + + let Debugger { + console_logger, + mut trace_collector, + .. + } = debugger; + + let mut initial_estimation = match result { + ExecutionResult::Success { gas_used, .. } => Ok(gas_used), + ExecutionResult::Revert { output, .. } => Err(TransactionFailure::revert( + output, + None, + trace_collector + .traces() + .first() + .expect("Must have a trace") + .clone(), + )), + ExecutionResult::Halt { reason, .. } => Err(TransactionFailure::halt( + reason, + None, + trace_collector + .traces() + .first() + .expect("Must have a trace") + .clone(), + )), } - } + .map_err(|failure| EstimateGasFailure { + console_log_inputs: console_logger.into_encoded_messages(), + transaction_failure: TransactionFailureWithTraces { + traces: vec![failure.solidity_trace.clone()], + failure, + }, + })?; - Ok(()) - } + // Ensure that the initial estimation is at least the minimum cost + 1. + if initial_estimation <= minimum_cost { + initial_estimation = minimum_cost + 1; + } - fn current_state(&mut self) -> Result>>, ProviderError> { - self.get_or_compute_state(self.last_block_number()) - } + // Test if the transaction would be successful with the initial estimation + let success = gas::check_gas_limit(CheckGasLimitArgs { + blockchain, + header, + state, + state_overrides: &state_overrides, + cfg_env: cfg_env.clone(), + transaction: transaction.clone(), + gas_limit: initial_estimation, + precompiles: &precompiles, + trace_collector: &mut trace_collector, + })?; - fn get_or_compute_state( - &mut self, - block_number: u64, - ) -> Result>>, ProviderError> { - if let Some(state_id) = self.block_number_to_state_id.get(&block_number) { - // We cannot use `LruCache::try_get_or_insert`, because it needs &mut self, but - // we would need &self in the callback to reference the blockchain. - if let Some(state) = self.block_state_cache.get(state_id) { - return Ok(state.clone()); + // Return the initial estimation if it was successful + if success { + return Ok(EstimateGasResult { + estimation: initial_estimation, + traces: trace_collector.into_traces(), + }); } - }; - let state = self - .blockchain - .state_at_block_number(block_number, self.irregular_state.state_overrides())?; - let state_id = self.add_state_to_cache(state, block_number); - Ok(self - .block_state_cache - .get(&state_id) - // State must exist, since we just inserted it, and we have exclusive access to - // the cache due to &mut self. - .expect("State must exist") - .clone()) - } + // Correct the initial estimation if the transaction failed with the actually + // used gas limit. This can happen if the execution logic is based + // on the available gas. + let estimation = gas::binary_search_estimation(BinarySearchEstimationArgs { + blockchain, + header, + state, + state_overrides: &state_overrides, + cfg_env: cfg_env.clone(), + transaction, + lower_bound: initial_estimation, + upper_bound: header.gas_limit, + precompiles: &precompiles, + trace_collector: &mut trace_collector, + })?; - fn add_state_to_cache( - &mut self, - state: Box>, - block_number: u64, - ) -> StateId { - let state_id = self.current_state_id.increment(); - self.block_state_cache.push(state_id, Arc::new(state)); - self.block_number_to_state_id - .insert_mut(block_number, state_id); - state_id + let traces = trace_collector.into_traces(); + Ok(EstimateGasResult { estimation, traces }) + })? } } @@ -2347,10 +2482,10 @@ impl StateId { } } -fn block_time_offset_seconds( - config: &ProviderConfig, +fn block_time_offset_seconds>( + config: &ProviderConfig, timer: &impl TimeSinceEpoch, -) -> Result> { +) -> Result> { config.initial_date.map_or(Ok(0), |initial_date| { let initial_timestamp = i64::try_from( initial_date @@ -2367,10 +2502,10 @@ fn block_time_offset_seconds( }) } -struct BlockchainAndState { - blockchain: Box, StateError>>, +struct BlockchainAndState { + blockchain: Box, StateError>>, fork_metadata: Option, - rpc_client: Option>>, + rpc_client: Option>>, state: Box>, irregular_state: IrregularState, prev_randao_generator: RandomHashGenerator, @@ -2378,12 +2513,12 @@ struct BlockchainAndState { next_block_base_fee_per_gas: Option, } -fn create_blockchain_and_state( +fn create_blockchain_and_state>( runtime: runtime::Handle, - config: &ProviderConfig, + config: &ProviderConfig, timer: &impl TimeSinceEpoch, mut genesis_accounts: HashMap, -) -> Result> { +) -> Result, CreationError> { let mut prev_randao_generator = RandomHashGenerator::with_seed(edr_defaults::MIX_HASH_SEED); if let Some(fork_config) = &config.fork { @@ -2397,16 +2532,16 @@ fn create_blockchain_and_state( .map(|headers| HeaderMap::try_from(headers).map_err(CreationError::InvalidHttpHeaders)) .transpose()?; - let rpc_client = Arc::new(EthRpcClient::::new( + let rpc_client = Arc::new(EthRpcClient::::new( &fork_config.json_rpc_url, config.cache_dir.clone(), http_headers.clone(), )?); let (blockchain, mut irregular_state) = - tokio::task::block_in_place(|| -> Result<_, ForkedCreationError> { + tokio::task::block_in_place(|| -> Result<_, ForkedCreationError> { let mut irregular_state = IrregularState::default(); - let blockchain = runtime.block_on(ForkedBlockchain::::new( + let blockchain = runtime.block_on(ForkedBlockchain::::new( runtime.clone(), Some(config.chain_id), config.hardfork, @@ -2488,7 +2623,7 @@ fn create_blockchain_and_state( .expect("Elapsed time since fork block must be representable as i64") }; - let next_block_base_fee_per_gas = if config.hardfork >= SpecId::LONDON { + let next_block_base_fee_per_gas = if config.hardfork.into() >= SpecId::LONDON { if let Some(base_fee) = config.initial_base_fee_per_gas { Some(base_fee) } else { @@ -2527,7 +2662,7 @@ fn create_blockchain_and_state( next_block_base_fee_per_gas, }) } else { - let mix_hash = if config.hardfork >= SpecId::MERGE { + let mix_hash = if config.hardfork.into() >= SpecId::MERGE { Some(prev_randao_generator.generate_next()) } else { None @@ -2572,28 +2707,13 @@ fn create_blockchain_and_state( } } -/// The result returned by requesting a transaction. -#[derive(Debug, Clone)] -pub struct TransactionAndBlock { - /// The transaction. - pub transaction: transaction::Signed, - /// Block data in which the transaction is found if it has been mined. - pub block_data: Option, - /// Whether the transaction is pending - pub is_pending: bool, -} - -/// Block metadata for a transaction. -#[derive(Debug, Clone)] -pub struct BlockDataForTransaction { - pub block: Arc>>, - pub transaction_index: u64, -} - #[cfg(test)] pub(crate) mod test_utils { use anyhow::anyhow; - use edr_eth::transaction::{self, TxKind}; + use edr_eth::{ + chain_spec::L1ChainSpec, + transaction::{self, TxKind}, + }; use super::*; use crate::{ @@ -2604,7 +2724,7 @@ pub(crate) mod test_utils { pub(crate) struct ProviderTestFixture { _runtime: runtime::Runtime, pub config: ProviderConfig, - pub provider_data: ProviderData, + pub provider_data: ProviderData, pub impersonated_account: Address, } @@ -2684,7 +2804,7 @@ pub(crate) mod test_utils { local_account_index: usize, gas_limit: u64, nonce: Option, - ) -> anyhow::Result { + ) -> anyhow::Result> { let request = transaction::Request::Eip155(transaction::request::Eip155 { kind: TxKind::Call(Address::ZERO), gas_limit, @@ -2734,7 +2854,7 @@ pub(crate) mod test_utils { #[cfg(test)] mod tests { use anyhow::Context; - use edr_eth::{hex, transaction::SignedTransaction as _}; + use edr_eth::{chain_spec::L1ChainSpec, hex, transaction::ExecutableTransaction as _}; use edr_evm::MineOrdering; use serde_json::json; @@ -2931,7 +3051,7 @@ mod tests { .provider_data .execute_in_block_context(block_spec.as_ref(), |_, _, _| { value += 1; - Ok::<(), ProviderError>(()) + Ok::<(), ProviderError>(()) })?; assert_eq!(value, 1); @@ -3774,10 +3894,10 @@ mod tests { /// /// Should return a string `"Hello World"`. fn call_hello_world_contract( - data: &mut ProviderData, + data: &mut ProviderData, block_spec: BlockSpec, request: CallRequest, - ) -> Result { + ) -> Result, ProviderError> { let state_overrides = StateOverrides::default(); let transaction = diff --git a/crates/edr_provider/src/data/account.rs b/crates/edr_provider/src/data/account.rs index f42a82129..2e11ac636 100644 --- a/crates/edr_provider/src/data/account.rs +++ b/crates/edr_provider/src/data/account.rs @@ -1,9 +1,9 @@ use edr_eth::{ - chain_spec::L1ChainSpec, signature::public_key_to_address, state::{Account, AccountStatus}, AccountInfo, Address, HashMap, KECCAK_EMPTY, }; +use edr_evm::chain_spec::ChainSpec; use indexmap::IndexMap; use crate::{AccountConfig, ProviderConfig}; @@ -13,7 +13,9 @@ pub(super) struct InitialAccounts { pub genesis_accounts: HashMap, } -pub(super) fn create_accounts(config: &ProviderConfig) -> InitialAccounts { +pub(super) fn create_accounts( + config: &ProviderConfig, +) -> InitialAccounts { let mut local_accounts = IndexMap::default(); let genesis_accounts = config diff --git a/crates/edr_provider/src/data/call.rs b/crates/edr_provider/src/data/call.rs index 70b27e14a..c5a66f662 100644 --- a/crates/edr_provider/src/data/call.rs +++ b/crates/edr_provider/src/data/call.rs @@ -1,38 +1,40 @@ +use core::fmt::Debug; + use edr_eth::{ - block::{BlobGas, Header}, - chain_spec::L1ChainSpec, - env::{BlobExcessGasAndPrice, BlockEnv}, - result::ExecutionResult, - Address, HashMap, Precompile, SpecId, U256, + block::Header, + result::{ExecutionResult, InvalidTransaction}, + transaction::TransactionValidation, + Address, HashMap, Precompile, U256, }; use edr_evm::{ blockchain::{BlockchainError, SyncBlockchain}, + chain_spec::{BlockEnvConstructor as _, ChainSpec, SyncChainSpec}, evm::handler::CfgEnvWithEvmWiring, guaranteed_dry_run, state::{StateError, StateOverrides, StateRefOverrider, SyncState}, - transaction, DebugContext, + DebugContext, }; use crate::ProviderError; -pub(super) struct RunCallArgs<'a, 'evm, DebugDataT> +pub(super) struct RunCallArgs<'a, 'evm, ChainSpecT: ChainSpec, DebugDataT> where 'a: 'evm, { - pub blockchain: &'a dyn SyncBlockchain, StateError>, + pub blockchain: &'a dyn SyncBlockchain, StateError>, pub header: &'a Header, pub state: &'a dyn SyncState, pub state_overrides: &'a StateOverrides, - pub cfg_env: CfgEnvWithEvmWiring, - pub transaction: transaction::Signed, + pub cfg_env: CfgEnvWithEvmWiring, + pub transaction: ChainSpecT::Transaction, pub precompiles: &'a HashMap, // `DebugContext` cannot be simplified further #[allow(clippy::type_complexity)] pub debug_context: Option< DebugContext< 'evm, - L1ChainSpec, - BlockchainError, + ChainSpecT, + BlockchainError, DebugDataT, StateRefOverrider<'a, &'evm dyn SyncState>, >, @@ -40,11 +42,16 @@ where } /// Execute a transaction as a call. Returns the gas used and the output. -pub(super) fn run_call<'a, 'evm, DebugDataT>( - args: RunCallArgs<'a, 'evm, DebugDataT>, -) -> Result, ProviderError> +pub(super) fn run_call<'a, 'evm, ChainSpecT, DebugDataT>( + args: RunCallArgs<'a, 'evm, ChainSpecT, DebugDataT>, +) -> Result, ProviderError> where 'a: 'evm, + ChainSpecT: SyncChainSpec< + Block: Default, + Hardfork: Debug, + Transaction: Default + TransactionValidation>, + >, { let RunCallArgs { blockchain, @@ -57,23 +64,11 @@ where debug_context, } = args; - let block = BlockEnv { - number: U256::from(header.number), - coinbase: header.beneficiary, - timestamp: U256::from(header.timestamp), - gas_limit: U256::from(header.gas_limit), - basefee: U256::ZERO, - difficulty: header.difficulty, - prevrandao: if cfg_env.spec_id >= SpecId::MERGE { - Some(header.mix_hash) - } else { - None - }, - blob_excess_gas_and_price: header - .blob_gas - .as_ref() - .map(|BlobGas { excess_gas, .. }| BlobExcessGasAndPrice::new(*excess_gas)), - }; + // `eth_call` uses a base fee of zero to mimick geth's behavior + let mut header = header.clone(); + header.base_fee_per_gas = header.base_fee_per_gas.map(|_| U256::ZERO); + + let block = ChainSpecT::Block::new_block_env(&header, cfg_env.spec_id); guaranteed_dry_run( blockchain, diff --git a/crates/edr_provider/src/data/gas.rs b/crates/edr_provider/src/data/gas.rs index a8f3ae4bb..62a5d832b 100644 --- a/crates/edr_provider/src/data/gas.rs +++ b/crates/edr_provider/src/data/gas.rs @@ -1,19 +1,19 @@ -use std::cmp; +use core::{cmp, fmt::Debug}; use edr_eth::{ block::Header, - chain_spec::L1ChainSpec, - result::ExecutionResult, + result::{ExecutionResult, InvalidTransaction}, reward_percentile::RewardPercentile, - transaction::{Transaction as _, TransactionMut as _}, + transaction::{Transaction as _, TransactionMut, TransactionValidation}, Address, HashMap, Precompile, U256, }; use edr_evm::{ blockchain::{BlockchainError, SyncBlockchain}, + chain_spec::{ChainSpec, SyncChainSpec}, evm::handler::CfgEnvWithEvmWiring, state::{StateError, StateOverrides, SyncState}, trace::{register_trace_collector_handles, TraceCollector}, - transaction, DebugContext, SyncBlock, + DebugContext, SyncBlock, }; use itertools::Itertools; @@ -22,22 +22,33 @@ use crate::{ ProviderError, }; -pub(super) struct CheckGasLimitArgs<'a> { - pub blockchain: &'a dyn SyncBlockchain, StateError>, +pub(super) struct CheckGasLimitArgs<'a, ChainSpecT: SyncChainSpec> { + pub blockchain: &'a dyn SyncBlockchain, StateError>, pub header: &'a Header, pub state: &'a dyn SyncState, pub state_overrides: &'a StateOverrides, - pub cfg_env: CfgEnvWithEvmWiring, - pub transaction: transaction::Signed, + pub cfg_env: CfgEnvWithEvmWiring, + pub transaction: ChainSpecT::Transaction, pub gas_limit: u64, pub precompiles: &'a HashMap, - pub trace_collector: &'a mut TraceCollector, + pub trace_collector: &'a mut TraceCollector, } /// Test if the transaction successfully executes with the given gas limit. /// Returns true on success and return false if the transaction runs out of gas /// or funds or reverts. Returns an error for any other halt reason. -pub(super) fn check_gas_limit(args: CheckGasLimitArgs<'_>) -> Result { +pub(super) fn check_gas_limit( + args: CheckGasLimitArgs<'_, ChainSpecT>, +) -> Result> +where + ChainSpecT: SyncChainSpec< + Block: Default, + Hardfork: Debug, + Transaction: Default + + TransactionMut + + TransactionValidation>, + >, +{ let CheckGasLimitArgs { blockchain, header, @@ -69,25 +80,34 @@ pub(super) fn check_gas_limit(args: CheckGasLimitArgs<'_>) -> Result { - pub blockchain: &'a dyn SyncBlockchain, StateError>, +pub(super) struct BinarySearchEstimationArgs<'a, ChainSpecT: SyncChainSpec> { + pub blockchain: &'a dyn SyncBlockchain, StateError>, pub header: &'a Header, pub state: &'a dyn SyncState, pub state_overrides: &'a StateOverrides, - pub cfg_env: CfgEnvWithEvmWiring, - pub transaction: transaction::Signed, + pub cfg_env: CfgEnvWithEvmWiring, + pub transaction: ChainSpecT::Transaction, pub lower_bound: u64, pub upper_bound: u64, pub precompiles: &'a HashMap, - pub trace_collector: &'a mut TraceCollector, + pub trace_collector: &'a mut TraceCollector, } /// Search for a tight upper bound on the gas limit that will allow the /// transaction to execute. Matches Hardhat logic, except it's iterative, not /// recursive. -pub(super) fn binary_search_estimation( - args: BinarySearchEstimationArgs<'_>, -) -> Result { +pub(super) fn binary_search_estimation( + args: BinarySearchEstimationArgs<'_, ChainSpecT>, +) -> Result> +where + ChainSpecT: SyncChainSpec< + Block: Default, + Hardfork: Debug, + Transaction: Default + + TransactionMut + + TransactionValidation>, + >, +{ const MAX_ITERATIONS: usize = 20; let BinarySearchEstimationArgs { @@ -157,10 +177,10 @@ fn min_difference(lower_bound: u64) -> u64 { } /// Compute miner rewards for percentiles. -pub(super) fn compute_rewards( - block: &dyn SyncBlock>, +pub(super) fn compute_rewards>( + block: &dyn SyncBlock>, reward_percentiles: &[RewardPercentile], -) -> Result, ProviderError> { +) -> Result, ProviderError> { if block.transactions().is_empty() { return Ok(reward_percentiles.iter().map(|_| U256::ZERO).collect()); } diff --git a/crates/edr_provider/src/debug_mine.rs b/crates/edr_provider/src/debug_mine.rs index 0603d35e0..1cfe61f4b 100644 --- a/crates/edr_provider/src/debug_mine.rs +++ b/crates/edr_provider/src/debug_mine.rs @@ -2,7 +2,7 @@ use core::fmt::Debug; use std::sync::Arc; use derive_where::derive_where; -use edr_eth::{result::ExecutionResult, transaction::SignedTransaction, Bytes, B256}; +use edr_eth::{result::ExecutionResult, transaction::ExecutableTransaction, Bytes, B256}; use edr_evm::{ chain_spec::ChainSpec, state::{StateDiff, SyncState}, diff --git a/crates/edr_provider/src/error.rs b/crates/edr_provider/src/error.rs index 1c5516405..78af3227a 100644 --- a/crates/edr_provider/src/error.rs +++ b/crates/edr_provider/src/error.rs @@ -2,27 +2,32 @@ use core::fmt::Debug; use std::num::TryFromIntError; use alloy_sol_types::{ContractError, SolInterface}; +use derive_where::derive_where; use edr_eth::{ - chain_spec::L1ChainSpec, + chain_spec::{EvmWiring, L1ChainSpec}, filter::SubscriptionType, hex, - result::{ExecutionResult, HaltReason, InvalidTransaction, OutOfGasError}, + result::{ExecutionResult, HaltReason, OutOfGasError}, Address, BlockSpec, BlockTag, Bytes, SpecId, B256, U256, }; use edr_evm::{ blockchain::BlockchainError, + chain_spec::ChainSpec, state::{AccountOverrideConversionError, StateError}, trace::Trace, transaction::{self, TransactionError}, - BlockTransactionError, DebugTraceError, MemPoolAddTransactionError, MineBlockError, - MineTransactionError, + DebugTraceError, MemPoolAddTransactionError, MineBlockError, MineTransactionError, }; use edr_rpc_eth::{client::RpcClientError, jsonrpc}; +use serde::Serialize; use crate::{data::CreationError, IntervalConfigConversionError}; #[derive(Debug, thiserror::Error)] -pub enum ProviderError { +pub enum ProviderError +where + ChainSpecT: ChainSpec, +{ /// Account override conversion error. #[error(transparent)] AccountOverrideConversionError(#[from] AccountOverrideConversionError), @@ -52,11 +57,11 @@ pub enum ProviderError { BlobMemPoolUnsupported, /// Blockchain error #[error(transparent)] - Blockchain(#[from] BlockchainError), + Blockchain(#[from] BlockchainError), #[error(transparent)] - Creation(#[from] CreationError), + Creation(#[from] CreationError), #[error(transparent)] - DebugTrace(#[from] DebugTraceError, StateError>), + DebugTrace(#[from] DebugTraceError, StateError>), #[error("An EIP-4844 (shard blob) call request was received, but Hardhat only supports them via `eth_sendRawTransaction`. See https://github.com/NomicFoundation/hardhat/issues/5182")] Eip4844CallRequestUnsupported, #[error("An EIP-4844 (shard blob) transaction was received, but Hardhat only supports them via `eth_sendRawTransaction`. See https://github.com/NomicFoundation/hardhat/issues/5023")] @@ -67,7 +72,7 @@ pub enum ProviderError { Eip712Error(#[from] alloy_dyn_abi::Error), /// A transaction error occurred while estimating gas. #[error(transparent)] - EstimateGasTransactionFailure(#[from] EstimateGasFailure), + EstimateGasTransactionFailure(#[from] EstimateGasFailure), #[error("{0}")] InvalidArgument(String), /// Block number or hash doesn't exist in blockchain @@ -122,11 +127,11 @@ pub enum ProviderError { MemPoolUpdate(StateError), /// An error occurred while mining a block. #[error(transparent)] - MineBlock(#[from] MineBlockError, StateError>), + MineBlock(#[from] MineBlockError, StateError>), /// An error occurred while mining a block with a single transaction. #[error(transparent)] MineTransaction( - #[from] MineTransactionError, StateError>, + #[from] MineTransactionError, StateError>, ), /// Rpc client error #[error(transparent)] @@ -136,7 +141,7 @@ pub enum ProviderError { RpcVersion(jsonrpc::Version), /// Error while running a transaction #[error(transparent)] - RunTransaction(#[from] TransactionError, StateError>), + RunTransaction(#[from] TransactionError, StateError>), /// The `hardhat_setMinGasPrice` method is not supported when EIP-1559 is /// active. #[error("hardhat_setMinGasPrice is not supported when EIP-1559 is active")] @@ -181,7 +186,7 @@ pub enum ProviderError { /// `eth_sendTransaction` failed and /// [`crate::config::ProviderConfig::bail_on_call_failure`] was enabled #[error(transparent)] - TransactionFailed(#[from] TransactionFailureWithTraces), + TransactionFailed(#[from] TransactionFailureWithTraces), /// Failed to convert an integer type #[error("Could not convert the integer argument, due to: {0}")] TryFromIntError(#[from] TryFromIntError), @@ -213,8 +218,11 @@ pub enum ProviderError { UnsupportedMethod { method_name: String }, } -impl From for jsonrpc::Error { - fn from(value: ProviderError) -> Self { +impl From> for jsonrpc::Error +where + ChainSpecT: ChainSpec, +{ + fn from(value: ProviderError) -> Self { const INVALID_INPUT: i16 = -32000; const INTERNAL_ERROR: i16 = -32603; const INVALID_PARAMS: i16 = -32602; @@ -293,34 +301,7 @@ impl From for jsonrpc::Error { _ => None, }; - let message = match &value { - ProviderError::DebugTrace(DebugTraceError::TransactionError(error)) - | ProviderError::MineBlock(MineBlockError::BlockTransaction( - BlockTransactionError::Transaction(error), - )) - | ProviderError::MineTransaction(MineTransactionError::BlockTransaction( - BlockTransactionError::Transaction(error), - )) - | ProviderError::RunTransaction(error) => { - if let TransactionError::InvalidTransaction( - InvalidTransaction::LackOfFundForMaxFee { fee, balance }, - ) = error - { - format!("Sender doesn't have enough funds to send tx. The max upfront cost is: {fee} and the sender's balance is: {balance}.") - } else { - value.to_string() - } - } - ProviderError::TransactionFailed(inner) - if matches!( - inner.failure.reason, - TransactionFailureReason::Inner(HaltReason::CreateContractSizeLimit) - ) => - { - "Transaction reverted: trying to deploy a contract whose code is too large".into() - } - _ => value.to_string(), - }; + let message = value.to_string(); Self { code, @@ -331,25 +312,28 @@ impl From for jsonrpc::Error { } /// Failure that occurred while estimating gas. -#[derive(Debug, thiserror::Error)] -pub struct EstimateGasFailure { +#[derive(thiserror::Error)] +#[derive_where(Debug; ChainSpecT, ChainSpecT::HaltReason)] +pub struct EstimateGasFailure { pub console_log_inputs: Vec, - pub transaction_failure: TransactionFailureWithTraces, + pub transaction_failure: TransactionFailureWithTraces, } -impl std::fmt::Display for EstimateGasFailure { +impl std::fmt::Display for EstimateGasFailure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.transaction_failure) } } -#[derive(Clone, Debug, thiserror::Error)] -pub struct TransactionFailureWithTraces { - pub failure: TransactionFailure, - pub traces: Vec>, +#[derive(thiserror::Error)] +#[derive_where(Clone; ChainSpecT::HaltReason)] +#[derive_where(Debug; ChainSpecT, ChainSpecT::HaltReason)] +pub struct TransactionFailureWithTraces { + pub failure: TransactionFailure, + pub traces: Vec>, } -impl std::fmt::Display for TransactionFailureWithTraces { +impl std::fmt::Display for TransactionFailureWithTraces { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.failure) } @@ -357,21 +341,25 @@ impl std::fmt::Display for TransactionFailureWithTraces { /// Wrapper around [`edr_eth::result::HaltReason`] to convert error messages to /// match Hardhat. -#[derive(Clone, Debug, thiserror::Error, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct TransactionFailure { - pub reason: TransactionFailureReason, +#[derive(thiserror::Error, Serialize)] +#[derive_where(Clone; ChainSpecT::HaltReason)] +#[derive_where(Debug; ChainSpecT, ChainSpecT::HaltReason)] +#[serde(bound = "ChainSpecT::HaltReason: Serialize", rename_all = "camelCase")] +pub struct TransactionFailure { + pub reason: TransactionFailureReason, pub data: String, #[serde(skip)] - pub solidity_trace: Trace, + pub solidity_trace: Trace, pub transaction_hash: Option, } -impl TransactionFailure { +impl>>> + TransactionFailure +{ pub fn from_execution_result( - execution_result: &ExecutionResult, + execution_result: &ExecutionResult, transaction_hash: Option<&B256>, - solidity_trace: &Trace, + solidity_trace: &Trace, ) -> Option { match execution_result { ExecutionResult::Success { .. } => None, @@ -381,17 +369,32 @@ impl TransactionFailure { solidity_trace.clone(), )), ExecutionResult::Halt { reason, .. } => Some(Self::halt( - *reason, + reason.clone(), transaction_hash.copied(), solidity_trace.clone(), )), } } + pub fn halt( + halt: ChainSpecT::HaltReason, + tx_hash: Option, + solidity_trace: Trace, + ) -> Self { + Self { + reason: halt.into(), + data: "0x".to_string(), + solidity_trace, + transaction_hash: tx_hash, + } + } +} + +impl TransactionFailure { pub fn revert( output: Bytes, transaction_hash: Option, - solidity_trace: Trace, + solidity_trace: Trace, ) -> Self { let data = format!("0x{}", hex::encode(output.as_ref())); Self { @@ -401,32 +404,17 @@ impl TransactionFailure { transaction_hash, } } - - pub fn halt( - halt: HaltReason, - tx_hash: Option, - solidity_trace: Trace, - ) -> Self { - let reason = match halt { - HaltReason::OpcodeNotFound | HaltReason::InvalidFEOpcode => { - TransactionFailureReason::OpcodeNotFound - } - HaltReason::OutOfGas(error) => TransactionFailureReason::OutOfGas(error), - halt => TransactionFailureReason::Inner(halt), - }; - - Self { - reason, - data: "0x".to_string(), - solidity_trace, - transaction_hash: tx_hash, - } - } } -impl std::fmt::Display for TransactionFailure { +impl std::fmt::Display for TransactionFailure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.reason { + TransactionFailureReason::CreateContractSizeLimit => { + write!( + f, + "Transaction reverted: trying to deploy a contract whose code is too large" + ) + } TransactionFailureReason::Inner(halt) => write!(f, "{halt:?}"), TransactionFailureReason::OpcodeNotFound => { write!( @@ -440,14 +428,32 @@ impl std::fmt::Display for TransactionFailure { } } -#[derive(Clone, Debug, serde::Serialize)] -pub enum TransactionFailureReason { - Inner(HaltReason), +#[derive_where(Clone, Debug; ChainSpecT::HaltReason)] +#[derive(Serialize)] +#[serde(bound = "ChainSpecT::HaltReason: Serialize")] +pub enum TransactionFailureReason { + CreateContractSizeLimit, + Inner(ChainSpecT::HaltReason), OpcodeNotFound, OutOfGas(OutOfGasError), Revert(Bytes), } +impl From for TransactionFailureReason { + fn from(value: HaltReason) -> Self { + match value { + HaltReason::CreateContractSizeLimit => { + TransactionFailureReason::CreateContractSizeLimit + } + HaltReason::OpcodeNotFound | HaltReason::InvalidFEOpcode => { + TransactionFailureReason::OpcodeNotFound + } + HaltReason::OutOfGas(error) => TransactionFailureReason::OutOfGas(error), + halt => TransactionFailureReason::Inner(halt), + } + } +} + fn revert_error(output: &Bytes) -> String { if output.is_empty() { return "Transaction reverted without a reason".to_string(); diff --git a/crates/edr_provider/src/interval.rs b/crates/edr_provider/src/interval.rs index 41f429cff..67ae93c1b 100644 --- a/crates/edr_provider/src/interval.rs +++ b/crates/edr_provider/src/interval.rs @@ -1,5 +1,8 @@ -use std::sync::Arc; +use core::fmt::Debug; +use std::{marker::PhantomData, sync::Arc}; +use edr_eth::{result::InvalidTransaction, transaction::TransactionValidation}; +use edr_evm::chain_spec::ChainSpec; use tokio::{ runtime, sync::{oneshot, Mutex}, @@ -7,26 +10,40 @@ use tokio::{ time::Instant, }; -use crate::{data::ProviderData, time::TimeSinceEpoch, IntervalConfig, ProviderError}; +use crate::{ + data::ProviderData, spec::SyncProviderSpec, time::TimeSinceEpoch, IntervalConfig, ProviderError, +}; /// Type for interval mining on a separate thread. -pub struct IntervalMiner { - inner: Option, +pub struct IntervalMiner, TimerT> { + inner: Option>, runtime: runtime::Handle, + phantom: PhantomData, } /// Inner type for interval mining on a separate thread, required for /// implementation of `Drop`. -struct Inner { +struct Inner> { cancellation_sender: oneshot::Sender<()>, - background_task: JoinHandle>, + background_task: JoinHandle>>, } -impl IntervalMiner { - pub fn new( +impl< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, + > IntervalMiner +{ + pub fn new( runtime: runtime::Handle, config: IntervalConfig, - data: Arc>>, + data: Arc>>, ) -> Self { let (cancellation_sender, cancellation_receiver) = oneshot::channel(); let background_task = runtime @@ -38,16 +55,27 @@ impl IntervalMiner { background_task, }), runtime, + phantom: PhantomData, } } } #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] -async fn interval_mining_loop( +async fn interval_mining_loop< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( config: IntervalConfig, - data: Arc>>, + data: Arc>>, mut cancellation_receiver: oneshot::Receiver<()>, -) -> Result<(), ProviderError> { +) -> Result<(), ProviderError> { let mut now = Instant::now(); loop { let delay = config.generate_interval(); @@ -67,7 +95,7 @@ async fn interval_mining_loop( return Err(error); } - Result::<(), ProviderError>::Ok(()) + Result::<(), ProviderError>::Ok(()) } } }, @@ -75,7 +103,7 @@ async fn interval_mining_loop( } } -impl Drop for IntervalMiner { +impl, TimerT> Drop for IntervalMiner { #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] fn drop(&mut self) { if let Some(Inner { diff --git a/crates/edr_provider/src/lib.rs b/crates/edr_provider/src/lib.rs index c26cdd4ca..3469b57cb 100644 --- a/crates/edr_provider/src/lib.rs +++ b/crates/edr_provider/src/lib.rs @@ -11,6 +11,8 @@ mod mock; mod pending; mod requests; mod snapshot; +/// Types for provider-related chain specification. +pub mod spec; mod subscribe; /// Utilities for testing #[cfg(any(test, feature = "test-utils"))] @@ -21,8 +23,12 @@ pub mod time; use core::fmt::Debug; use std::sync::Arc; -use edr_eth::{chain_spec::L1ChainSpec, HashSet}; -use edr_evm::{blockchain::BlockchainError, trace::Trace}; +use edr_eth::{ + result::InvalidTransaction, + transaction::{IsEip155, IsEip4844, TransactionMut, TransactionType, TransactionValidation}, + HashSet, +}; +use edr_evm::{blockchain::BlockchainError, chain_spec::ChainSpec, trace::Trace}; use lazy_static::lazy_static; use logger::SyncLogger; use mock::SyncCallOverride; @@ -42,6 +48,7 @@ pub use self::{ hardhat::rpc_types as hardhat_rpc_types, IntervalConfig as IntervalConfigRequest, InvalidRequestReason, MethodInvocation, ProviderRequest, Timestamp, }, + spec::{ProviderSpec, SyncProviderSpec}, subscribe::*, }; use self::{ @@ -62,9 +69,9 @@ lazy_static! { } #[derive(Clone, Debug)] -pub struct ResponseWithTraces { +pub struct ResponseWithTraces { pub result: serde_json::Value, - pub traces: Vec>, + pub traces: Vec>, } /// A JSON-RPC provider for Ethereum. @@ -84,7 +91,7 @@ pub struct ResponseWithTraces { /// /// fn to_response( /// id: jsonrpc::Id, -/// result: Result, /// ) -> jsonrpc::Response { let data = match result { /// Ok(result) => jsonrpc::ResponseData::Success { result }, Err(error) => /// jsonrpc::ResponseData::Error { error: jsonrpc::Error { code: -32000, @@ -97,24 +104,66 @@ pub struct ResponseWithTraces { /// } /// } /// ``` -pub struct Provider { - data: Arc>>, +pub struct Provider, TimerT: Clone + TimeSinceEpoch = CurrentTime> +{ + data: Arc>>, /// Interval miner runs in the background, if enabled. It holds the data /// mutex, so it needs to internally check for cancellation/self-destruction /// while async-awaiting the lock to avoid a deadlock. - interval_miner: Arc>>, + interval_miner: Arc>>>, runtime: runtime::Handle, } -impl Provider { +impl, TimerT: Clone + TimeSinceEpoch> + Provider +{ + pub fn set_call_override_callback(&self, call_override: Option>) { + let mut data = task::block_in_place(|| self.runtime.block_on(self.data.lock())); + data.set_call_override_callback(call_override); + } + + /// Set to `true` to make the traces returned with `eth_call`, + /// `eth_estimateGas`, `eth_sendRawTransaction`, `eth_sendTransaction`, + /// `evm_mine`, `hardhat_mine` include the full stack and memory. Set to + /// `false` to disable this. + pub fn set_verbose_tracing(&self, verbose_tracing: bool) { + let mut data = task::block_in_place(|| self.runtime.block_on(self.data.lock())); + data.set_verbose_tracing(verbose_tracing); + } + + /// Blocking method to log a failed deserialization. + pub fn log_failed_deserialization( + &self, + method_name: &str, + error: &ProviderError, + ) -> Result<(), ProviderError> { + let mut data = task::block_in_place(|| self.runtime.block_on(self.data.lock())); + data.logger_mut() + .print_method_logs(method_name, Some(error)) + .map_err(ProviderError::Logger) + } +} + +impl< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, + > Provider +{ /// Constructs a new instance. pub fn new( runtime: runtime::Handle, - logger: Box>>, - subscriber_callback: Box, - config: ProviderConfig, + logger: Box>>, + subscriber_callback: Box>, + config: ProviderConfig, timer: TimerT, - ) -> Result> { + ) -> Result> { let data = ProviderData::new( runtime.clone(), logger, @@ -139,26 +188,28 @@ impl Provider { runtime, }) } +} - pub fn set_call_override_callback(&self, call_override: Option>) { - let mut data = task::block_in_place(|| self.runtime.block_on(self.data.lock())); - data.set_call_override_callback(call_override); - } - - /// Set to `true` to make the traces returned with `eth_call`, - /// `eth_estimateGas`, `eth_sendRawTransaction`, `eth_sendTransaction`, - /// `evm_mine`, `hardhat_mine` include the full stack and memory. Set to - /// `false` to disable this. - pub fn set_verbose_tracing(&self, verbose_tracing: bool) { - let mut data = task::block_in_place(|| self.runtime.block_on(self.data.lock())); - data.set_verbose_tracing(verbose_tracing); - } - +impl< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Clone + Default, + PooledTransaction: IsEip155, + Transaction: Default + + TransactionMut + + TransactionType + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, + > Provider +{ /// Blocking method to handle a request. pub fn handle_request( &self, - request: ProviderRequest, - ) -> Result { + request: ProviderRequest, + ) -> Result, ProviderError> { let mut data = task::block_in_place(|| self.runtime.block_on(self.data.lock())); match request { @@ -167,24 +218,12 @@ impl Provider { } } - /// Blocking method to log a failed deserialization. - pub fn log_failed_deserialization( - &self, - method_name: &str, - error: &ProviderError, - ) -> Result<(), ProviderError> { - let mut data = task::block_in_place(|| self.runtime.block_on(self.data.lock())); - data.logger_mut() - .print_method_logs(method_name, Some(error)) - .map_err(ProviderError::Logger) - } - /// Handles a batch of JSON requests for an execution provider. fn handle_batch_request( &self, - data: &mut ProviderData, - request: Vec, - ) -> Result { + data: &mut ProviderData, + request: Vec>, + ) -> Result, ProviderError> { let mut results = Vec::new(); let mut traces = Vec::new(); @@ -200,9 +239,9 @@ impl Provider { fn handle_single_request( &self, - data: &mut ProviderData, - request: MethodInvocation, - ) -> Result { + data: &mut ProviderData, + request: MethodInvocation, + ) -> Result, ProviderError> { let method_name = if data.logger_mut().is_enabled() { let method_name = request.method_name(); if PRIVATE_RPC_METHODS.contains(method_name) { @@ -463,9 +502,9 @@ impl Provider { fn reset( &self, - data: &mut ProviderData, + data: &mut ProviderData, config: Option, - ) -> Result { + ) -> Result> { let mut interval_miner = self.interval_miner.lock(); interval_miner.take(); @@ -479,7 +518,9 @@ impl Provider { } } -fn to_json(value: T) -> Result { +fn to_json>( + value: T, +) -> Result, ProviderError> { let response = serde_json::to_value(value).map_err(ProviderError::Serialization)?; Ok(ResponseWithTraces { @@ -488,9 +529,9 @@ fn to_json(value: T) -> Result( - value: (T, Trace), -) -> Result { +fn to_json_with_trace>( + value: (T, Trace), +) -> Result, ProviderError> { let response = serde_json::to_value(value.0).map_err(ProviderError::Serialization)?; Ok(ResponseWithTraces { @@ -499,9 +540,9 @@ fn to_json_with_trace( }) } -fn to_json_with_traces( - value: (T, Vec>), -) -> Result { +fn to_json_with_traces>( + value: (T, Vec>), +) -> Result, ProviderError> { let response = serde_json::to_value(value.0).map_err(ProviderError::Serialization)?; Ok(ResponseWithTraces { diff --git a/crates/edr_provider/src/logger.rs b/crates/edr_provider/src/logger.rs index f60e5bf82..36f12e615 100644 --- a/crates/edr_provider/src/logger.rs +++ b/crates/edr_provider/src/logger.rs @@ -1,15 +1,15 @@ +use core::fmt::Debug; use std::marker::PhantomData; use derive_where::derive_where; use dyn_clone::DynClone; -use edr_eth::transaction; use edr_evm::{blockchain::BlockchainError, chain_spec::ChainSpec}; use crate::{ data::CallResult, debug_mine::DebugMineBlockResult, error::EstimateGasFailure, ProviderError, }; -pub trait Logger { +pub trait Logger> { type BlockchainError; /// Whether the logger is enabled. @@ -21,8 +21,8 @@ pub trait Logger { fn log_call( &mut self, spec_id: edr_eth::SpecId, - transaction: &transaction::Signed, - result: &CallResult, + transaction: &ChainSpecT::Transaction, + result: &CallResult, ) -> Result<(), Box> { let _spec_id = spec_id; let _transaction = transaction; @@ -34,8 +34,8 @@ pub trait Logger { fn log_estimate_gas_failure( &mut self, spec_id: edr_eth::SpecId, - transaction: &transaction::Signed, - result: &EstimateGasFailure, + transaction: &ChainSpecT::Transaction, + result: &EstimateGasFailure, ) -> Result<(), Box> { let _spec_id = spec_id; let _transaction = transaction; @@ -69,7 +69,7 @@ pub trait Logger { fn log_send_transaction( &mut self, spec_id: edr_eth::SpecId, - transaction: &transaction::Signed, + transaction: &ChainSpecT::Transaction, mining_results: &[DebugMineBlockResult], ) -> Result<(), Box> { let _spec_id = spec_id; @@ -86,20 +86,23 @@ pub trait Logger { fn print_method_logs( &mut self, method: &str, - error: Option<&ProviderError>, + error: Option<&ProviderError>, ) -> Result<(), Box>; } -pub trait SyncLogger: Logger + DynClone + Send + Sync {} +pub trait SyncLogger>: + Logger + DynClone + Send + Sync +{ +} impl SyncLogger for T where - ChainSpecT: ChainSpec, + ChainSpecT: ChainSpec, T: Logger + DynClone + Send + Sync, { } -impl Clone +impl, BlockchainErrorT> Clone for Box> { fn clone(&self) -> Self { @@ -113,7 +116,7 @@ pub struct NoopLogger { _phantom: PhantomData, } -impl Logger for NoopLogger { +impl> Logger for NoopLogger { type BlockchainError = BlockchainError; fn is_enabled(&self) -> bool { @@ -125,7 +128,7 @@ impl Logger for NoopLogger { fn print_method_logs( &mut self, _method: &str, - _error: Option<&ProviderError>, + _error: Option<&ProviderError>, ) -> Result<(), Box> { Ok(()) } diff --git a/crates/edr_provider/src/pending.rs b/crates/edr_provider/src/pending.rs index 3f1956c20..b2a7f23ae 100644 --- a/crates/edr_provider/src/pending.rs +++ b/crates/edr_provider/src/pending.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, sync::Arc}; use derive_where::derive_where; -use edr_eth::{db::BlockHashRef, transaction::SignedTransaction as _, HashSet, B256, U256}; +use edr_eth::{db::BlockHashRef, transaction::ExecutableTransaction as _, HashSet, B256, U256}; use edr_evm::{ blockchain::{Blockchain, BlockchainError, BlockchainMut, SyncBlockchain}, chain_spec::SyncChainSpec, diff --git a/crates/edr_provider/src/requests.rs b/crates/edr_provider/src/requests.rs index ec22f8fae..9f4d33e8c 100644 --- a/crates/edr_provider/src/requests.rs +++ b/crates/edr_provider/src/requests.rs @@ -4,15 +4,18 @@ pub mod eth; /// Hardhat RPC request types pub mod hardhat; mod methods; +mod resolve; mod serde; -mod validation; +pub(crate) mod validation; -use std::fmt; +use std::{fmt, marker::PhantomData}; use ::serde::{ de::{self, MapAccess, SeqAccess, Visitor}, Deserialize, Deserializer, Serialize, }; +use derive_where::derive_where; +use edr_rpc_eth::spec::RpcSpec; pub use crate::requests::{ methods::{IntervalConfig, MethodInvocation}, @@ -20,27 +23,31 @@ pub use crate::requests::{ }; /// JSON-RPC request for the provider. -#[derive(Clone, Debug, Serialize)] -#[serde(untagged)] -pub enum ProviderRequest { +#[derive(Serialize)] +#[derive_where(Clone, Debug; ChainSpecT::RpcCallRequest, ChainSpecT::RpcTransactionRequest)] +#[serde(bound = "")] +pub enum ProviderRequest { /// A single JSON-RPC request - Single(MethodInvocation), + Single(MethodInvocation), /// A batch of requests - Batch(Vec), + Batch(Vec>), } // Custom deserializer for `ProviderRequest` instead of using // `#[serde(untagged)]` as the latter hides custom error messages which are // important to propagate to users. -impl<'de> Deserialize<'de> for ProviderRequest { +impl<'de, ChainSpecT: RpcSpec> Deserialize<'de> for ProviderRequest { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - struct SingleOrBatchRequestVisitor; + #[derive_where(Default)] + struct SingleOrBatchRequestVisitor { + phantom: PhantomData, + } - impl<'de> Visitor<'de> for SingleOrBatchRequestVisitor { - type Value = ProviderRequest; + impl<'de, ChainSpecT: RpcSpec> Visitor<'de> for SingleOrBatchRequestVisitor { + type Value = ProviderRequest; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("single or batch request") @@ -56,7 +63,7 @@ impl<'de> Deserialize<'de> for ProviderRequest { )?)) } - fn visit_map(self, map: M) -> Result + fn visit_map(self, map: M) -> Result, M::Error> where M: MapAccess<'de>, { @@ -67,13 +74,14 @@ impl<'de> Deserialize<'de> for ProviderRequest { } } - deserializer.deserialize_any(SingleOrBatchRequestVisitor) + deserializer.deserialize_any(SingleOrBatchRequestVisitor::::default()) } } #[cfg(test)] mod tests { use anyhow::Context; + use edr_eth::chain_spec::L1ChainSpec; use super::*; @@ -85,7 +93,7 @@ mod tests { "params": ["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"], "id": 1 }"#; - let request: ProviderRequest = serde_json::from_str(json)?; + let request: ProviderRequest = serde_json::from_str(json)?; assert!(matches!( request, ProviderRequest::Single(MethodInvocation::GetBalance(..)) @@ -109,7 +117,7 @@ mod tests { "id": 2 } ]"#; - let request: ProviderRequest = serde_json::from_str(json)?; + let request: ProviderRequest = serde_json::from_str(json)?; assert!(matches!(request, ProviderRequest::Batch(_))); Ok(()) } @@ -119,7 +127,7 @@ mod tests { let s = "foo"; let json = format!(r#""{s}""#); - let result: Result = serde_json::from_str(&json); + let result: Result, _> = serde_json::from_str(&json); let error_message = result.err().context("result is error")?.to_string(); assert!(error_message.contains(s)); diff --git a/crates/edr_provider/src/requests/debug.rs b/crates/edr_provider/src/requests/debug.rs index 03499b272..624aa9893 100644 --- a/crates/edr_provider/src/requests/debug.rs +++ b/crates/edr_provider/src/requests/debug.rs @@ -1,25 +1,30 @@ -use core::fmt::Debug; - -use edr_eth::{chain_spec::L1ChainSpec, BlockSpec, B256}; +use edr_eth::{result::InvalidTransaction, transaction::TransactionValidation, BlockSpec, B256}; use edr_evm::{state::StateOverrides, trace::Trace, DebugTraceResult, DebugTraceResultWithTraces}; -use edr_rpc_eth::CallRequest; use serde::{Deserialize, Deserializer}; use crate::{ data::ProviderData, - requests::{ - eth::{resolve_block_spec_for_call_request, resolve_call_request}, - validation::validate_call_request, - }, + requests::eth::{resolve_block_spec_for_call_request, resolve_call_request}, + spec::SyncProviderSpec, time::TimeSinceEpoch, ProviderError, }; -pub fn handle_debug_trace_transaction( - data: &mut ProviderData, +pub fn handle_debug_trace_transaction< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Clone + Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, transaction_hash: B256, config: Option, -) -> Result<(DebugTraceResult, Vec>), ProviderError> { +) -> Result<(DebugTraceResult, Vec>), ProviderError> { let DebugTraceResultWithTraces { result, traces } = data .debug_trace_transaction( &transaction_hash, @@ -35,14 +40,25 @@ pub fn handle_debug_trace_transaction( Ok((result, traces)) } -pub fn handle_debug_trace_call( - data: &mut ProviderData, - call_request: CallRequest, +pub fn handle_debug_trace_call( + data: &mut ProviderData, + call_request: ChainSpecT::RpcCallRequest, block_spec: Option, config: Option, -) -> Result<(DebugTraceResult, Vec>), ProviderError> { +) -> Result<(DebugTraceResult, Vec>), ProviderError> +where + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + + TimerT: Clone + TimeSinceEpoch, +{ let block_spec = resolve_block_spec_for_call_request(block_spec); - validate_call_request(data.spec_id(), &call_request, &block_spec)?; let transaction = resolve_call_request(data, call_request, &block_spec, &StateOverrides::default())?; diff --git a/crates/edr_provider/src/requests/eth/accounts.rs b/crates/edr_provider/src/requests/eth/accounts.rs index 6bd2fb185..5d843c0c9 100644 --- a/crates/edr_provider/src/requests/eth/accounts.rs +++ b/crates/edr_provider/src/requests/eth/accounts.rs @@ -1,11 +1,11 @@ use edr_eth::Address; -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; +use crate::{data::ProviderData, spec::ProviderSpec, time::TimeSinceEpoch, ProviderError}; /// `require_canonical`: whether the server should additionally raise a JSON-RPC /// error if the block is not in the canonical chain -pub fn handle_accounts_request( - data: &ProviderData, -) -> Result, ProviderError> { +pub fn handle_accounts_request, TimerT: Clone + TimeSinceEpoch>( + data: &ProviderData, +) -> Result, ProviderError> { Ok(data.accounts().copied().collect()) } diff --git a/crates/edr_provider/src/requests/eth/blockchain.rs b/crates/edr_provider/src/requests/eth/blockchain.rs index 4eae6cfc2..6a112a4c0 100644 --- a/crates/edr_provider/src/requests/eth/blockchain.rs +++ b/crates/edr_provider/src/requests/eth/blockchain.rs @@ -1,29 +1,47 @@ -use edr_eth::{Address, BlockSpec, U256, U64}; +use edr_eth::{ + result::InvalidTransaction, transaction::TransactionValidation, Address, BlockSpec, U256, U64, +}; use crate::{ - data::ProviderData, requests::validation::validate_post_merge_block_tags, time::TimeSinceEpoch, - ProviderError, + data::ProviderData, requests::validation::validate_post_merge_block_tags, + spec::SyncProviderSpec, time::TimeSinceEpoch, ProviderError, }; -pub fn handle_block_number_request( - data: &ProviderData, -) -> Result { +pub fn handle_block_number_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, +) -> Result> { Ok(U64::from(data.last_block_number())) } -pub fn handle_chain_id_request( - data: &ProviderData, -) -> Result { +pub fn handle_chain_id_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, +) -> Result> { Ok(U64::from(data.chain_id())) } -pub fn handle_get_transaction_count_request( - data: &mut ProviderData, +pub fn handle_get_transaction_count_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, address: Address, block_spec: Option, -) -> Result { +) -> Result> { if let Some(block_spec) = block_spec.as_ref() { - validate_post_merge_block_tags(data.spec_id(), block_spec)?; + validate_post_merge_block_tags(data.evm_spec_id(), block_spec)?; } data.get_transaction_count(address, block_spec.as_ref()) diff --git a/crates/edr_provider/src/requests/eth/blocks.rs b/crates/edr_provider/src/requests/eth/blocks.rs index f6c535e6a..207e9acc8 100644 --- a/crates/edr_provider/src/requests/eth/blocks.rs +++ b/crates/edr_provider/src/requests/eth/blocks.rs @@ -2,36 +2,44 @@ use core::fmt::Debug; use std::sync::Arc; use edr_eth::{ - chain_spec::L1ChainSpec, transaction::SignedTransaction as _, BlockSpec, PreEip1898BlockSpec, - SpecId, B256, U256, U64, + result::InvalidTransaction, + transaction::{ExecutableTransaction as _, TransactionValidation}, + BlockSpec, PreEip1898BlockSpec, B256, U256, U64, }; -use edr_evm::{blockchain::BlockchainError, SyncBlock}; +use edr_evm::{ + block::transaction::{BlockDataForTransaction, TransactionAndBlock}, + blockchain::BlockchainError, + chain_spec::ChainSpec, + SyncBlock, +}; +use edr_rpc_eth::RpcTypeFrom as _; use crate::{ - data::{BlockDataForTransaction, ProviderData, TransactionAndBlock}, - requests::{eth::transaction_to_rpc_result, validation::validate_post_merge_block_tags}, - time::TimeSinceEpoch, - ProviderError, + data::ProviderData, requests::validation::validate_post_merge_block_tags, + spec::SyncProviderSpec, time::TimeSinceEpoch, ProviderError, }; #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] #[serde(untagged)] -pub enum HashOrTransaction { +pub enum HashOrTransaction { Hash(B256), - Transaction(edr_rpc_eth::Transaction), + Transaction(ChainSpecT::RpcTransaction), } -pub fn handle_get_block_by_hash_request( - data: &ProviderData, +pub fn handle_get_block_by_hash_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, block_hash: B256, transaction_detail_flag: bool, -) -> Result>, ProviderError> { +) -> Result>>, ProviderError> { data.block_by_hash(&block_hash)? .map(|block| { let total_difficulty = data.total_difficulty_by_hash(block.hash())?; let pending = false; block_to_rpc_output( - data.spec_id(), + data.hardfork(), block, pending, total_difficulty, @@ -41,11 +49,21 @@ pub fn handle_get_block_by_hash_request( .transpose() } -pub fn handle_get_block_by_number_request( - data: &mut ProviderData, +pub fn handle_get_block_by_number_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, block_spec: PreEip1898BlockSpec, transaction_detail_flag: bool, -) -> Result>, ProviderError> { +) -> Result>>, ProviderError> { block_by_number(data, &block_spec.into())? .map( |BlockByNumberResult { @@ -54,7 +72,7 @@ pub fn handle_get_block_by_number_request( total_difficulty, }| { block_to_rpc_output( - data.spec_id(), + data.hardfork(), block, pending, total_difficulty, @@ -65,39 +83,62 @@ pub fn handle_get_block_by_number_request( .transpose() } -pub fn handle_get_block_transaction_count_by_hash_request( - data: &ProviderData, +pub fn handle_get_block_transaction_count_by_hash_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, block_hash: B256, -) -> Result, ProviderError> { +) -> Result, ProviderError> { Ok(data .block_by_hash(&block_hash)? .map(|block| U64::from(block.transactions().len()))) } -pub fn handle_get_block_transaction_count_by_block_number( - data: &mut ProviderData, +pub fn handle_get_block_transaction_count_by_block_number< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, block_spec: PreEip1898BlockSpec, -) -> Result, ProviderError> { +) -> Result, ProviderError> { Ok(block_by_number(data, &block_spec.into())? .map(|BlockByNumberResult { block, .. }| U64::from(block.transactions().len()))) } /// The result returned by requesting a block by number. #[derive(Debug, Clone)] -struct BlockByNumberResult { +struct BlockByNumberResult { /// The block - pub block: Arc>>, + pub block: Arc>>, /// Whether the block is a pending block. pub pending: bool, /// The total difficulty with the block pub total_difficulty: Option, } -fn block_by_number( - data: &mut ProviderData, +fn block_by_number< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, block_spec: &BlockSpec, -) -> Result, ProviderError> { - validate_post_merge_block_tags(data.spec_id(), block_spec)?; +) -> Result>, ProviderError> { + validate_post_merge_block_tags(data.evm_spec_id(), block_spec)?; match data.block_by_block_spec(block_spec) { Ok(Some(block)) => { @@ -111,7 +152,7 @@ fn block_by_number( // Pending block Ok(None) => { let result = data.mine_pending_block()?; - let block: Arc>> = + let block: Arc>> = Arc::new(result.block); let last_block = data.last_block()?; @@ -131,16 +172,16 @@ fn block_by_number( } } -fn block_to_rpc_output( - spec_id: SpecId, - block: Arc>>, - pending: bool, +fn block_to_rpc_output>( + hardfork: ChainSpecT::Hardfork, + block: Arc>>, + is_pending: bool, total_difficulty: Option, transaction_detail_flag: bool, -) -> Result, ProviderError> { +) -> Result>, ProviderError> { let header = block.header(); - let transactions: Vec = if transaction_detail_flag { + let transactions: Vec> = if transaction_detail_flag { block .transactions() .iter() @@ -151,10 +192,13 @@ fn block_to_rpc_output( block: block.clone(), transaction_index: i.try_into().expect("usize fits into u64"), }), - is_pending: false, + is_pending, + }) + .map(|transaction_and_block: TransactionAndBlock| { + ChainSpecT::RpcTransaction::rpc_type_from(&transaction_and_block, hardfork) }) - .map(|tx| transaction_to_rpc_result(tx, spec_id).map(HashOrTransaction::Transaction)) - .collect::>()? + .map(HashOrTransaction::Transaction) + .collect() } else { block .transactions() @@ -163,9 +207,17 @@ fn block_to_rpc_output( .collect() }; - let mix_hash = if pending { None } else { Some(header.mix_hash) }; - let nonce = if pending { None } else { Some(header.nonce) }; - let number = if pending { None } else { Some(header.number) }; + let mix_hash = if is_pending { + None + } else { + Some(header.mix_hash) + }; + let nonce = if is_pending { None } else { Some(header.nonce) }; + let number = if is_pending { + None + } else { + Some(header.number) + }; Ok(edr_rpc_eth::Block { hash: Some(*block.hash()), diff --git a/crates/edr_provider/src/requests/eth/call.rs b/crates/edr_provider/src/requests/eth/call.rs index 8d0e569fb..0f979bd62 100644 --- a/crates/edr_provider/src/requests/eth/call.rs +++ b/crates/edr_provider/src/requests/eth/call.rs @@ -1,20 +1,36 @@ -use edr_eth::{chain_spec::L1ChainSpec, BlockSpec, Bytes, SpecId, U256}; +use edr_eth::{ + result::InvalidTransaction, + transaction::{signed::FakeSign as _, TransactionValidation}, + BlockSpec, Bytes, SpecId, U256, +}; use edr_evm::{state::StateOverrides, trace::Trace, transaction}; -use edr_rpc_eth::{CallRequest, StateOverrideOptions}; +use edr_rpc_eth::StateOverrideOptions; use crate::{ - data::ProviderData, requests::validation::validate_call_request, time::TimeSinceEpoch, + data::ProviderData, + spec::{CallContext, MaybeSender as _, ResolveRpcType, SyncProviderSpec}, + time::TimeSinceEpoch, ProviderError, TransactionFailure, }; -pub fn handle_call_request( - data: &mut ProviderData, - request: CallRequest, +pub fn handle_call_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Clone + + Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, + request: ChainSpecT::RpcCallRequest, block_spec: Option, state_overrides: Option, -) -> Result<(Bytes, Trace), ProviderError> { +) -> Result<(Bytes, Trace), ProviderError> { let block_spec = resolve_block_spec_for_call_request(block_spec); - validate_call_request(data.spec_id(), &request, &block_spec)?; let state_overrides = state_overrides.map_or(Ok(StateOverrides::default()), StateOverrides::try_from)?; @@ -22,9 +38,9 @@ pub fn handle_call_request( let transaction = resolve_call_request(data, request, &block_spec, &state_overrides)?; let result = data.run_call(transaction.clone(), &block_spec, &state_overrides)?; - let spec_id = data.spec_id(); + let evm_spec_id = data.evm_spec_id(); data.logger_mut() - .log_call(spec_id, &transaction, &result) + .log_call(evm_spec_id, &transaction, &result) .map_err(ProviderError::Logger)?; if data.bail_on_call_failure() { @@ -48,19 +64,33 @@ pub(crate) fn resolve_block_spec_for_call_request(block_spec: Option) block_spec.unwrap_or_else(BlockSpec::latest) } -pub(crate) fn resolve_call_request( - data: &mut ProviderData, - request: CallRequest, +pub(crate) fn resolve_call_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, + request: ChainSpecT::RpcCallRequest, block_spec: &BlockSpec, state_overrides: &StateOverrides, -) -> Result { - resolve_call_request_inner( +) -> Result> { + let sender = request + .maybe_sender() + .copied() + .unwrap_or_else(|| data.default_caller()); + + let context = CallContext { data, - request, block_spec, state_overrides, - |_data| Ok(U256::ZERO), - |_, max_fee_per_gas, max_priority_fee_per_gas| { + default_gas_price_fn: |_data| Ok(U256::ZERO), + max_fees_fn: |_data, _block_spec, max_fee_per_gas, max_priority_fee_per_gas| { let max_fee_per_gas = max_fee_per_gas .or(max_priority_fee_per_gas) .unwrap_or(U256::ZERO); @@ -69,160 +99,11 @@ pub(crate) fn resolve_call_request( Ok((max_fee_per_gas, max_priority_fee_per_gas)) }, - ) -} - -pub(crate) fn resolve_call_request_inner( - data: &mut ProviderData, - request: CallRequest, - block_spec: &BlockSpec, - state_overrides: &StateOverrides, - default_gas_price_fn: impl FnOnce(&ProviderData) -> Result, - max_fees_fn: impl FnOnce( - &ProviderData, - // max_fee_per_gas - Option, - // max_priority_fee_per_gas - Option, - ) -> Result<(U256, U256), ProviderError>, -) -> Result { - let CallRequest { - from, - to, - gas, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - value, - data: input, - access_list, - .. - } = request; - - let chain_id = data.chain_id(); - let from = from.unwrap_or_else(|| data.default_caller()); - let gas_limit = gas.unwrap_or_else(|| data.block_gas_limit()); - let input = input.map_or(Bytes::new(), Bytes::from); - let nonce = data.nonce(&from, Some(block_spec), state_overrides)?; - let value = value.unwrap_or(U256::ZERO); - - let transaction = if data.spec_id() < SpecId::LONDON || gas_price.is_some() { - let gas_price = gas_price.map_or_else(|| default_gas_price_fn(data), Ok)?; - match access_list { - Some(access_list) if data.spec_id() >= SpecId::BERLIN => { - transaction::Request::Eip2930(transaction::request::Eip2930 { - nonce, - gas_price, - gas_limit, - value, - input, - kind: to.into(), - chain_id, - access_list, - }) - } - _ => transaction::Request::Eip155(transaction::request::Eip155 { - nonce, - gas_price, - gas_limit, - kind: to.into(), - value, - input, - chain_id, - }), - } - } else { - let (max_fee_per_gas, max_priority_fee_per_gas) = - max_fees_fn(data, max_fee_per_gas, max_priority_fee_per_gas)?; - - transaction::Request::Eip1559(transaction::request::Eip1559 { - chain_id, - nonce, - max_fee_per_gas, - max_priority_fee_per_gas, - gas_limit, - kind: to.into(), - value, - input, - access_list: access_list.unwrap_or_default(), - }) }; - let transaction = transaction.fake_sign(from); + let request = request.resolve_rpc_type(context)?; + let transaction = request.fake_sign(sender); transaction::validate(transaction, SpecId::LATEST) .map_err(ProviderError::TransactionCreationError) } - -#[cfg(test)] -mod tests { - use edr_eth::transaction::Transaction as _; - - use super::*; - use crate::{data::test_utils::ProviderTestFixture, test_utils::pending_base_fee}; - - #[test] - fn resolve_call_request_inner_with_gas_price() -> anyhow::Result<()> { - let mut fixture = ProviderTestFixture::new_local()?; - - let pending_base_fee = pending_base_fee(&mut fixture.provider_data)?; - - let request = CallRequest { - from: Some(fixture.nth_local_account(0)?), - to: Some(fixture.nth_local_account(1)?), - gas_price: Some(pending_base_fee), - ..CallRequest::default() - }; - - let resolved = resolve_call_request_inner( - &mut fixture.provider_data, - request, - &BlockSpec::pending(), - &StateOverrides::default(), - |_data| unreachable!("gas_price is set"), - |_, _, _| unreachable!("gas_price is set"), - )?; - - assert_eq!(*resolved.gas_price(), pending_base_fee); - - Ok(()) - } - - #[test] - fn resolve_call_request_inner_with_max_fee_and_max_priority_fee() -> anyhow::Result<()> { - let mut fixture = ProviderTestFixture::new_local()?; - - let max_fee_per_gas = pending_base_fee(&mut fixture.provider_data)?; - let max_priority_fee_per_gas = Some(max_fee_per_gas / U256::from(2)); - - let request = CallRequest { - from: Some(fixture.nth_local_account(0)?), - to: Some(fixture.nth_local_account(1)?), - max_fee_per_gas: Some(max_fee_per_gas), - max_priority_fee_per_gas, - ..CallRequest::default() - }; - - let resolved = resolve_call_request_inner( - &mut fixture.provider_data, - request, - &BlockSpec::pending(), - &StateOverrides::default(), - |_data| unreachable!("max fees are set"), - |_, max_fee_per_gas, max_priority_fee_per_gas| { - Ok(( - max_fee_per_gas.expect("max fee is set"), - max_priority_fee_per_gas.expect("max priority fee is set"), - )) - }, - )?; - - assert_eq!(*resolved.gas_price(), max_fee_per_gas); - assert_eq!( - resolved.max_priority_fee_per_gas().cloned(), - max_priority_fee_per_gas - ); - - Ok(()) - } -} diff --git a/crates/edr_provider/src/requests/eth/config.rs b/crates/edr_provider/src/requests/eth/config.rs index b06c5af38..518ba20b1 100644 --- a/crates/edr_provider/src/requests/eth/config.rs +++ b/crates/edr_provider/src/requests/eth/config.rs @@ -1,50 +1,69 @@ +use core::fmt::Debug; + use edr_eth::{Address, U256, U64}; +use edr_evm::chain_spec::ChainSpec; -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; +use crate::{ + data::ProviderData, + spec::{ProviderSpec, SyncProviderSpec}, + time::TimeSinceEpoch, + ProviderError, +}; -pub fn handle_blob_base_fee( - data: &ProviderData, -) -> Result { +pub fn handle_blob_base_fee< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, +) -> Result> { let base_fee = data.next_block_base_fee_per_blob_gas()?.unwrap_or_default(); Ok(base_fee) } -pub fn handle_gas_price( - data: &ProviderData, -) -> Result { +pub fn handle_gas_price, TimerT: Clone + TimeSinceEpoch>( + data: &ProviderData, +) -> Result> { data.gas_price() } -pub fn handle_coinbase_request( - data: &ProviderData, -) -> Result { +pub fn handle_coinbase_request, TimerT: Clone + TimeSinceEpoch>( + data: &ProviderData, +) -> Result> { Ok(data.coinbase()) } -pub fn handle_max_priority_fee_per_gas() -> Result { +pub fn handle_max_priority_fee_per_gas>( +) -> Result> { // 1 gwei Ok(U256::from(1_000_000_000)) } -pub fn handle_mining() -> Result { +pub fn handle_mining>( +) -> Result> { Ok(false) } -pub fn handle_net_listening_request() -> Result { +pub fn handle_net_listening_request>( +) -> Result> { Ok(true) } -pub fn handle_net_peer_count_request() -> Result { +pub fn handle_net_peer_count_request>( +) -> Result> { Ok(U64::from(0)) } -pub fn handle_net_version_request( - data: &ProviderData, -) -> Result { +pub fn handle_net_version_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, +) -> Result> { Ok(data.network_id()) } -pub fn handle_syncing() -> Result { +pub fn handle_syncing>( +) -> Result> { Ok(false) } diff --git a/crates/edr_provider/src/requests/eth/evm.rs b/crates/edr_provider/src/requests/eth/evm.rs index fe053855f..edd799f6d 100644 --- a/crates/edr_provider/src/requests/eth/evm.rs +++ b/crates/edr_provider/src/requests/eth/evm.rs @@ -1,24 +1,44 @@ use std::num::NonZeroU64; -use edr_eth::{block::BlockOptions, chain_spec::L1ChainSpec, U64}; +use edr_eth::{ + block::BlockOptions, result::InvalidTransaction, transaction::TransactionValidation, U64, +}; use edr_evm::trace::Trace; -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError, Timestamp}; - -pub fn handle_increase_time_request( - data: &mut ProviderData, +use crate::{ + data::ProviderData, + spec::{ProviderSpec, SyncProviderSpec}, + time::TimeSinceEpoch, + ProviderError, Timestamp, +}; + +pub fn handle_increase_time_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, increment: Timestamp, -) -> Result { +) -> Result> { let new_block_time = data.increase_block_time(increment.into()); // This RPC call is an exception: it returns a number as a string decimal Ok(new_block_time.to_string()) } -pub fn handle_mine_request( - data: &mut ProviderData, +pub fn handle_mine_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, timestamp: Option, -) -> Result<(String, Vec>), ProviderError> { +) -> Result<(String, Vec>), ProviderError> { let mine_block_result = data.mine_and_commit_block(BlockOptions { timestamp: timestamp.map(Into::into), ..BlockOptions::default() @@ -26,7 +46,7 @@ pub fn handle_mine_request( let traces = mine_block_result.transaction_traces.clone(); - let spec_id = data.spec_id(); + let spec_id = data.evm_spec_id(); data.logger_mut() .log_mined_block(spec_id, &[mine_block_result]) .map_err(ProviderError::Logger)?; @@ -35,26 +55,32 @@ pub fn handle_mine_request( Ok((result, traces)) } -pub fn handle_revert_request( - data: &mut ProviderData, +pub fn handle_revert_request, TimerT: Clone + TimeSinceEpoch>( + data: &mut ProviderData, snapshot_id: U64, -) -> Result { +) -> Result> { Ok(data.revert_to_snapshot(snapshot_id.as_limbs()[0])) } -pub fn handle_set_automine_request( - data: &mut ProviderData, +pub fn handle_set_automine_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, automine: bool, -) -> Result { +) -> Result> { data.set_auto_mining(automine); Ok(true) } -pub fn handle_set_block_gas_limit_request( - data: &mut ProviderData, +pub fn handle_set_block_gas_limit_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, gas_limit: U64, -) -> Result { +) -> Result> { let gas_limit = NonZeroU64::new(gas_limit.as_limbs()[0]) .ok_or(ProviderError::SetBlockGasLimitMustBeGreaterThanZero)?; @@ -63,19 +89,25 @@ pub fn handle_set_block_gas_limit_request( Ok(true) } -pub fn handle_set_next_block_timestamp_request( - data: &mut ProviderData, +pub fn handle_set_next_block_timestamp_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, timestamp: Timestamp, -) -> Result { +) -> Result> { let new_timestamp = data.set_next_block_timestamp(timestamp.into())?; // This RPC call is an exception: it returns a number as a string decimal Ok(new_timestamp.to_string()) } -pub fn handle_snapshot_request( - data: &mut ProviderData, -) -> Result { +pub fn handle_snapshot_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, +) -> Result> { let snapshot_id = data.make_snapshot(); Ok(U64::from(snapshot_id)) diff --git a/crates/edr_provider/src/requests/eth/filter.rs b/crates/edr_provider/src/requests/eth/filter.rs index 32dbc9b14..43ec1a415 100644 --- a/crates/edr_provider/src/requests/eth/filter.rs +++ b/crates/edr_provider/src/requests/eth/filter.rs @@ -1,4 +1,4 @@ -use std::iter; +use core::iter; use edr_eth::{ filter::{FilteredEvents, LogFilterOptions, LogOutput, OneOrMore, SubscriptionType}, @@ -6,72 +6,100 @@ use edr_eth::{ }; use crate::{ - data::ProviderData, filter::LogFilter, requests::validation::validate_post_merge_block_tags, - time::TimeSinceEpoch, ProviderError, + data::ProviderData, + filter::LogFilter, + requests::validation::validate_post_merge_block_tags, + spec::{ProviderSpec, SyncProviderSpec}, + time::TimeSinceEpoch, + ProviderError, }; -pub fn handle_get_filter_changes_request( - data: &mut ProviderData, +pub fn handle_get_filter_changes_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, filter_id: U256, -) -> Result, ProviderError> { +) -> Result, ProviderError> { Ok(data.get_filter_changes(&filter_id)) } -pub fn handle_get_filter_logs_request( - data: &mut ProviderData, +pub fn handle_get_filter_logs_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, filter_id: U256, -) -> Result>, ProviderError> { +) -> Result>, ProviderError> { data.get_filter_logs(&filter_id) } -pub fn handle_get_logs_request( - data: &ProviderData, +pub fn handle_get_logs_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, filter_options: LogFilterOptions, -) -> Result, ProviderError> { +) -> Result, ProviderError> { + let evm_spec_id = data.evm_spec_id(); // Hardhat integration tests expect validation in this order. if let Some(from_block) = &filter_options.from_block { - validate_post_merge_block_tags(data.spec_id(), from_block)?; + validate_post_merge_block_tags(evm_spec_id, from_block)?; } if let Some(to_block) = &filter_options.to_block { - validate_post_merge_block_tags(data.spec_id(), to_block)?; + validate_post_merge_block_tags(evm_spec_id, to_block)?; } - let filter = validate_filter_criteria::(data, filter_options)?; + let filter = validate_filter_criteria::(data, filter_options)?; data.logs(filter) .map(|logs| logs.iter().map(LogOutput::from).collect()) } -pub fn handle_new_block_filter_request( - data: &mut ProviderData, -) -> Result { +pub fn handle_new_block_filter_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, +) -> Result> { data.add_block_filter::() } -pub fn handle_new_log_filter_request( - data: &mut ProviderData, +pub fn handle_new_log_filter_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, filter_criteria: LogFilterOptions, -) -> Result { - let filter_criteria = validate_filter_criteria::(data, filter_criteria)?; +) -> Result> { + let filter_criteria = + validate_filter_criteria::(data, filter_criteria)?; data.add_log_filter::(filter_criteria) } -pub fn handle_new_pending_transaction_filter_request( - data: &mut ProviderData, -) -> Result { +pub fn handle_new_pending_transaction_filter_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, +) -> Result> { Ok(data.add_pending_transaction_filter::()) } -pub fn handle_subscribe_request( - data: &mut ProviderData, +pub fn handle_subscribe_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, subscription_type: SubscriptionType, filter_criteria: Option, -) -> Result { +) -> Result> { match subscription_type { SubscriptionType::Logs => { let filter_criteria = filter_criteria.ok_or_else(|| { ProviderError::InvalidArgument("Missing params argument".to_string()) })?; - let filter_criteria = validate_filter_criteria::(data, filter_criteria)?; + let filter_criteria = + validate_filter_criteria::(data, filter_criteria)?; data.add_log_filter::(filter_criteria) } SubscriptionType::NewHeads => data.add_block_filter::(), @@ -81,30 +109,43 @@ pub fn handle_subscribe_request( } } -pub fn handle_uninstall_filter_request( - data: &mut ProviderData, +pub fn handle_uninstall_filter_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, filter_id: U256, -) -> Result { +) -> Result> { Ok(data.remove_filter(&filter_id)) } -pub fn handle_unsubscribe_request( - data: &mut ProviderData, +pub fn handle_unsubscribe_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, filter_id: U256, -) -> Result { +) -> Result> { Ok(data.remove_subscription(&filter_id)) } -fn validate_filter_criteria( - data: &ProviderData, +fn validate_filter_criteria< + const SHOULD_RESOLVE_LATEST: bool, + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, filter: LogFilterOptions, -) -> Result { - fn normalize_block_spec( - data: &ProviderData, +) -> Result> { + fn normalize_block_spec< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, + >( + data: &ProviderData, block_spec: Option, - ) -> Result, ProviderError> { + ) -> Result, ProviderError> { if let Some(block_spec) = &block_spec { - validate_post_merge_block_tags(data.spec_id(), block_spec)?; + validate_post_merge_block_tags(data.evm_spec_id(), block_spec)?; } let block_number = match block_spec { diff --git a/crates/edr_provider/src/requests/eth/gas.rs b/crates/edr_provider/src/requests/eth/gas.rs index a275aeb28..11f83dca8 100644 --- a/crates/edr_provider/src/requests/eth/gas.rs +++ b/crates/edr_provider/src/requests/eth/gas.rs @@ -1,37 +1,49 @@ use edr_eth::{ - chain_spec::L1ChainSpec, fee_history::FeeHistoryResult, reward_percentile::RewardPercentile, - transaction, BlockSpec, SpecId, U256, U64, + fee_history::FeeHistoryResult, + result::InvalidTransaction, + reward_percentile::RewardPercentile, + transaction::{signed::FakeSign as _, TransactionMut, TransactionValidation}, + BlockSpec, SpecId, U256, U64, }; -use edr_evm::{state::StateOverrides, trace::Trace}; -use edr_rpc_eth::CallRequest; +use edr_evm::{state::StateOverrides, trace::Trace, transaction}; -use super::resolve_call_request_inner; use crate::{ data::ProviderData, - requests::validation::{validate_call_request, validate_post_merge_block_tags}, + requests::validation::validate_post_merge_block_tags, + spec::{CallContext, MaybeSender as _, ResolveRpcType as _, SyncProviderSpec}, time::TimeSinceEpoch, ProviderError, }; -pub fn handle_estimate_gas( - data: &mut ProviderData, - call_request: CallRequest, +pub fn handle_estimate_gas< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionMut + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, + request: ChainSpecT::RpcCallRequest, block_spec: Option, -) -> Result<(U64, Vec>), ProviderError> { +) -> Result<(U64, Vec>), ProviderError> { // Matching Hardhat behavior in defaulting to "pending" instead of "latest" for // estimate gas. let block_spec = block_spec.unwrap_or_else(BlockSpec::pending); - validate_call_request(data.spec_id(), &call_request, &block_spec)?; + let evm_spec_id = data.evm_spec_id(); let transaction = - resolve_estimate_gas_request(data, call_request, &block_spec, &StateOverrides::default())?; + resolve_estimate_gas_request(data, request, &block_spec, &StateOverrides::default())?; let result = data.estimate_gas(transaction.clone(), &block_spec); if let Err(ProviderError::EstimateGasTransactionFailure(failure)) = result { - let spec_id = data.spec_id(); data.logger_mut() - .log_estimate_gas_failure(spec_id, &transaction, &failure) + .log_estimate_gas_failure(evm_spec_id, &transaction, &failure) .map_err(ProviderError::Logger)?; Err(ProviderError::TransactionFailed( @@ -43,13 +55,23 @@ pub fn handle_estimate_gas( } } -pub fn handle_fee_history( - data: &mut ProviderData, +pub fn handle_fee_history< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, block_count: U256, newest_block: BlockSpec, reward_percentiles: Option>, -) -> Result { - if data.spec_id() < SpecId::LONDON { +) -> Result> { + if data.evm_spec_id() < SpecId::LONDON { return Err(ProviderError::InvalidInput( "eth_feeHistory is disabled. It only works with the London hardfork or a later one." .into(), @@ -70,7 +92,7 @@ pub fn handle_fee_history( )); } - validate_post_merge_block_tags(data.spec_id(), &newest_block)?; + validate_post_merge_block_tags(data.evm_spec_id(), &newest_block)?; let reward_percentiles = reward_percentiles.map(|percentiles| { let mut validated_percentiles = Vec::with_capacity(percentiles.len()); @@ -96,19 +118,33 @@ The reward percentiles should be in non-decreasing order, but the percentile num data.fee_history(block_count, &newest_block, reward_percentiles) } -fn resolve_estimate_gas_request( - data: &mut ProviderData, - request: CallRequest, +fn resolve_estimate_gas_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, + request: ChainSpecT::RpcCallRequest, block_spec: &BlockSpec, state_overrides: &StateOverrides, -) -> Result { - resolve_call_request_inner( +) -> Result> { + let sender = request + .maybe_sender() + .copied() + .unwrap_or_else(|| data.default_caller()); + + let context = CallContext { data, - request, block_spec, state_overrides, - ProviderData::gas_price, - |data, max_fee_per_gas, max_priority_fee_per_gas| { + default_gas_price_fn: ProviderData::gas_price, + max_fees_fn: |data, block_spec, max_fee_per_gas, max_priority_fee_per_gas| { let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or_else(|| { const DEFAULT: u64 = 1_000_000_000; let default = U256::from(DEFAULT); @@ -121,7 +157,7 @@ fn resolve_estimate_gas_request( }); let max_fee_per_gas = max_fee_per_gas.map_or_else( - || -> Result { + || -> Result> { let base_fee = if let Some(block) = data.block_by_block_spec(block_spec)? { max_priority_fee_per_gas + block.header().base_fee_per_gas.unwrap_or(U256::ZERO) @@ -141,12 +177,19 @@ fn resolve_estimate_gas_request( Ok((max_fee_per_gas, max_priority_fee_per_gas)) }, - ) + }; + + let request = request.resolve_rpc_type(context)?; + let transaction = request.fake_sign(sender); + + transaction::validate(transaction, SpecId::LATEST) + .map_err(ProviderError::TransactionCreationError) } #[cfg(test)] mod tests { use edr_eth::{transaction::Transaction as _, BlockTag}; + use edr_rpc_eth::CallRequest; use super::*; use crate::{data::test_utils::ProviderTestFixture, test_utils::pending_base_fee}; diff --git a/crates/edr_provider/src/requests/eth/mine.rs b/crates/edr_provider/src/requests/eth/mine.rs index 61608900c..55aeb7c0e 100644 --- a/crates/edr_provider/src/requests/eth/mine.rs +++ b/crates/edr_provider/src/requests/eth/mine.rs @@ -1,18 +1,29 @@ use std::sync::Arc; +use edr_eth::{result::InvalidTransaction, transaction::TransactionValidation}; use tokio::{runtime, sync::Mutex}; use crate::{ - data::ProviderData, interval::IntervalMiner, requests, time::TimeSinceEpoch, IntervalConfig, - ProviderError, + data::ProviderData, interval::IntervalMiner, requests, spec::SyncProviderSpec, + time::TimeSinceEpoch, IntervalConfig, ProviderError, }; -pub fn handle_set_interval_mining( - data: Arc>>, - interval_miner: &mut Option, +pub fn handle_set_interval_mining< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: Arc>>, + interval_miner: &mut Option>, runtime: runtime::Handle, config: requests::IntervalConfig, -) -> Result { +) -> Result> { let config: Option = config.try_into()?; *interval_miner = config.map(|config| IntervalMiner::new(runtime, config, data.clone())); diff --git a/crates/edr_provider/src/requests/eth/sign.rs b/crates/edr_provider/src/requests/eth/sign.rs index 275a87a6a..aa46ea5b3 100644 --- a/crates/edr_provider/src/requests/eth/sign.rs +++ b/crates/edr_provider/src/requests/eth/sign.rs @@ -1,20 +1,23 @@ use alloy_dyn_abi::eip712::TypedData; use edr_eth::{Address, Bytes}; -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; +use crate::{data::ProviderData, spec::ProviderSpec, time::TimeSinceEpoch, ProviderError}; -pub fn handle_sign_request( - data: &ProviderData, +pub fn handle_sign_request, TimerT: Clone + TimeSinceEpoch>( + data: &ProviderData, message: Bytes, address: Address, -) -> Result { +) -> Result> { Ok((&data.sign(&address, message)?).into()) } -pub fn handle_sign_typed_data_v4( - data: &ProviderData, +pub fn handle_sign_typed_data_v4< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, address: Address, message: TypedData, -) -> Result { +) -> Result> { Ok((&data.sign_typed_data_v4(&address, &message)?).into()) } diff --git a/crates/edr_provider/src/requests/eth/state.rs b/crates/edr_provider/src/requests/eth/state.rs index 9d0881c2d..da276b5b2 100644 --- a/crates/edr_provider/src/requests/eth/state.rs +++ b/crates/edr_provider/src/requests/eth/state.rs @@ -1,42 +1,75 @@ -use edr_eth::{utils::u256_to_padded_hex, Address, BlockSpec, Bytes, U256}; +use edr_eth::{ + result::InvalidTransaction, transaction::TransactionValidation, utils::u256_to_padded_hex, + Address, BlockSpec, Bytes, U256, +}; use crate::{ - data::ProviderData, requests::validation::validate_post_merge_block_tags, time::TimeSinceEpoch, - ProviderError, + data::ProviderData, requests::validation::validate_post_merge_block_tags, + spec::SyncProviderSpec, time::TimeSinceEpoch, ProviderError, }; -pub fn handle_get_balance_request( - data: &mut ProviderData, +pub fn handle_get_balance_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, address: Address, block_spec: Option, -) -> Result { +) -> Result> { if let Some(block_spec) = block_spec.as_ref() { - validate_post_merge_block_tags(data.spec_id(), block_spec)?; + validate_post_merge_block_tags(data.evm_spec_id(), block_spec)?; } data.balance(address, block_spec.as_ref()) } -pub fn handle_get_code_request( - data: &mut ProviderData, +pub fn handle_get_code_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, address: Address, block_spec: Option, -) -> Result { +) -> Result> { if let Some(block_spec) = block_spec.as_ref() { - validate_post_merge_block_tags(data.spec_id(), block_spec)?; + validate_post_merge_block_tags(data.evm_spec_id(), block_spec)?; } data.get_code(address, block_spec.as_ref()) } -pub fn handle_get_storage_at_request( - data: &mut ProviderData, +pub fn handle_get_storage_at_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, address: Address, index: U256, block_spec: Option, -) -> Result { +) -> Result> { if let Some(block_spec) = block_spec.as_ref() { - validate_post_merge_block_tags(data.spec_id(), block_spec)?; + validate_post_merge_block_tags(data.evm_spec_id(), block_spec)?; } let storage = data.get_storage_at(address, index, block_spec.as_ref())?; diff --git a/crates/edr_provider/src/requests/eth/transactions.rs b/crates/edr_provider/src/requests/eth/transactions.rs index 86a49a733..47fbb39e0 100644 --- a/crates/edr_provider/src/requests/eth/transactions.rs +++ b/crates/edr_provider/src/requests/eth/transactions.rs @@ -1,58 +1,81 @@ +use core::fmt::Debug; use std::sync::Arc; use edr_eth::{ - chain_spec::L1ChainSpec, + result::InvalidTransaction, rlp::Decodable, transaction::{ - pooled::PooledTransaction, request::TransactionRequestAndSender, EthTransactionRequest, - SignedTransaction, Transaction as _, TransactionType as _, TxKind, + request::TransactionRequestAndSender, IsEip155, IsEip4844, Transaction as _, + TransactionType, TransactionValidation, }, - Bytes, PreEip1898BlockSpec, SpecId, B256, U256, + Bytes, PreEip1898BlockSpec, B256, U256, }; -use edr_evm::{blockchain::BlockchainError, trace::Trace, transaction, SyncBlock}; -use edr_rpc_eth::receipt::ToRpcReceipt as _; +use edr_evm::{ + block::transaction::{BlockDataForTransaction, TransactionAndBlock}, + blockchain::BlockchainError, + chain_spec::ChainSpec, + trace::Trace, + transaction, SyncBlock, +}; +use edr_rpc_eth::RpcTypeFrom as _; use crate::{ - data::{BlockDataForTransaction, ProviderData, TransactionAndBlock}, + data::ProviderData, error::TransactionFailureWithTraces, requests::validation::{ validate_eip3860_max_initcode_size, validate_post_merge_block_tags, validate_transaction_and_call_request, }, + spec::{ResolveRpcType, Sender as _, SyncProviderSpec, TransactionContext}, time::TimeSinceEpoch, ProviderError, TransactionFailure, }; -const FIRST_HARDFORK_WITH_TRANSACTION_TYPE: SpecId = SpecId::BERLIN; - -pub fn handle_get_transaction_by_block_hash_and_index( - data: &ProviderData, +pub fn handle_get_transaction_by_block_hash_and_index< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, block_hash: B256, index: U256, -) -> Result, ProviderError> { +) -> Result, ProviderError> { let index = rpc_index_to_usize(&index)?; - data.block_by_hash(&block_hash)? + let transaction = data + .block_by_hash(&block_hash)? .and_then(|block| transaction_from_block(block, index, false)) - .map(|tx| transaction_to_rpc_result(tx, data.spec_id())) - .transpose() + .map(|transaction_and_block| { + ChainSpecT::RpcTransaction::rpc_type_from(&transaction_and_block, data.hardfork()) + }); + + Ok(transaction) } -pub fn handle_get_transaction_by_block_spec_and_index( - data: &mut ProviderData, +pub fn handle_get_transaction_by_block_spec_and_index< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, block_spec: PreEip1898BlockSpec, index: U256, -) -> Result, ProviderError> { - validate_post_merge_block_tags(data.spec_id(), &block_spec)?; +) -> Result, ProviderError> { + validate_post_merge_block_tags(data.evm_spec_id(), &block_spec)?; let index = rpc_index_to_usize(&index)?; - match data.block_by_block_spec(&block_spec.into()) { + let transaction = match data.block_by_block_spec(&block_spec.into()) { Ok(Some(block)) => Some((block, false)), // Pending block requested Ok(None) => { let result = data.mine_pending_block()?; - let block: Arc>> = + let block: Arc>> = Arc::new(result.block); Some((block, true)) } @@ -61,67 +84,75 @@ pub fn handle_get_transaction_by_block_spec_and_index return Err(err), } .and_then(|(block, is_pending)| transaction_from_block(block, index, is_pending)) - .map(|tx| transaction_to_rpc_result(tx, data.spec_id())) - .transpose() + .map(|transaction_and_block| { + ChainSpecT::RpcTransaction::rpc_type_from(&transaction_and_block, data.hardfork()) + }); + + Ok(transaction) } -pub fn handle_pending_transactions( - data: &ProviderData, -) -> Result, ProviderError> { - let spec_id = data.spec_id(); - data.pending_transactions() +pub fn handle_pending_transactions< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, +) -> Result, ProviderError> { + let transactions = data + .pending_transactions() .map(|pending_transaction| { let transaction_and_block = TransactionAndBlock { transaction: pending_transaction.clone(), block_data: None, is_pending: true, }; - transaction_to_rpc_result(transaction_and_block, spec_id) + ChainSpecT::RpcTransaction::rpc_type_from(&transaction_and_block, data.hardfork()) }) - .collect() + .collect(); + + Ok(transactions) } -fn rpc_index_to_usize(index: &U256) -> Result { +fn rpc_index_to_usize>( + index: &U256, +) -> Result> { index .try_into() .map_err(|_err| ProviderError::InvalidTransactionIndex(*index)) } -pub fn handle_get_transaction_by_hash( - data: &ProviderData, +pub fn handle_get_transaction_by_hash< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, transaction_hash: B256, -) -> Result, ProviderError> { - data.transaction_by_hash(&transaction_hash)? - .map(|tx| transaction_to_rpc_result(tx, data.spec_id())) - .transpose() +) -> Result, ProviderError> { + let transaction = data + .transaction_by_hash(&transaction_hash)? + .map(|transaction_and_block| { + ChainSpecT::RpcTransaction::rpc_type_from(&transaction_and_block, data.hardfork()) + }); + + Ok(transaction) } -pub fn handle_get_transaction_receipt( - data: &ProviderData, +pub fn handle_get_transaction_receipt< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, transaction_hash: B256, -) -> Result, ProviderError> { +) -> Result, ProviderError> { let receipt = data.transaction_receipt(&transaction_hash)?; - Ok(receipt.map(|receipt| receipt.to_rpc_receipt(data.spec_id()))) - - // The JSON-RPC layer should not return the gas price as effective gas price - // for receipts in pre-London hardforks. - // if let Some(receipt) = receipt.as_ref() { - // if data.spec_id() < SpecId::LONDON && - // receipt.effective_gas_price.is_some() { let mut receipt = - // (**receipt).clone(); receipt.inner.effective_gas_price = - // None; - - // return Ok(Some(Arc::new(receipt))); - // } - // } + Ok(receipt.map(|receipt| ChainSpecT::RpcReceipt::rpc_type_from(&receipt, data.hardfork()))) } -fn transaction_from_block( - block: Arc>>, +fn transaction_from_block( + block: Arc>>, transaction_index: usize, is_pending: bool, -) -> Option { +) -> Option> { block .transactions() .get(transaction_index) @@ -135,142 +166,51 @@ fn transaction_from_block( }) } -pub fn transaction_to_rpc_result( - transaction_and_block: TransactionAndBlock, - spec_id: SpecId, -) -> Result { - fn gas_price_for_post_eip1559( - signed_transaction: &transaction::Signed, - block: Option<&Arc>>>, - ) -> U256 { - let max_fee_per_gas = signed_transaction - .max_fee_per_gas() - .expect("Transaction must be post EIP-1559 transaction."); - let max_priority_fee_per_gas = *signed_transaction - .max_priority_fee_per_gas() - .expect("Transaction must be post EIP-1559 transaction."); - - if let Some(block) = block { - let base_fee_per_gas = block.header().base_fee_per_gas.expect( - "Transaction must have base fee per gas in block metadata if EIP-1559 is active.", - ); - let priority_fee_per_gas = - max_priority_fee_per_gas.min(max_fee_per_gas - base_fee_per_gas); - base_fee_per_gas + priority_fee_per_gas - } else { - // We are following Hardhat's behavior of returning the max fee per gas for - // pending transactions. - max_fee_per_gas - } - } - - let TransactionAndBlock { - transaction, - block_data, - is_pending, - } = transaction_and_block; - let block = block_data.as_ref().map(|b| &b.block); - let header = block.map(|b| b.header()); - - let gas_price = match &transaction { - transaction::Signed::PreEip155Legacy(tx) => tx.gas_price, - transaction::Signed::PostEip155Legacy(tx) => tx.gas_price, - transaction::Signed::Eip2930(tx) => tx.gas_price, - transaction::Signed::Eip1559(_) | transaction::Signed::Eip4844(_) => { - gas_price_for_post_eip1559(&transaction, block) - } - }; - - let chain_id = match &transaction { - // Following Hardhat in not returning `chain_id` for `PostEip155Legacy` legacy transactions - // even though the chain id would be recoverable. - transaction::Signed::PreEip155Legacy(_) | transaction::Signed::PostEip155Legacy(_) => None, - transaction::Signed::Eip2930(tx) => Some(tx.chain_id), - transaction::Signed::Eip1559(tx) => Some(tx.chain_id), - transaction::Signed::Eip4844(tx) => Some(tx.chain_id), - }; - - let show_transaction_type = spec_id >= FIRST_HARDFORK_WITH_TRANSACTION_TYPE; - let is_typed_transaction = transaction.transaction_type() > transaction::Type::Legacy; - let transaction_type = if show_transaction_type || is_typed_transaction { - Some(transaction.transaction_type()) - } else { - None - }; - - let signature = transaction.signature(); - let (block_hash, block_number) = if is_pending { - (None, None) - } else { - header - .map(|header| (header.hash(), U256::from(header.number))) - .unzip() - }; - - let transaction_index = if is_pending { - None - } else { - block_data.as_ref().map(|bd| bd.transaction_index) - }; - - let access_list = if transaction.transaction_type() >= transaction::Type::Eip2930 { - Some(transaction.access_list().to_vec()) - } else { - None - }; - - let blob_versioned_hashes = if transaction.transaction_type() == transaction::Type::Eip4844 { - Some(transaction.blob_hashes().to_vec()) - } else { - None - }; - - Ok(edr_rpc_eth::Transaction { - hash: *transaction.transaction_hash(), - nonce: transaction.nonce(), - block_hash, - block_number, - transaction_index, - from: *transaction.caller(), - to: transaction.kind().to().copied(), - value: *transaction.value(), - gas_price, - gas: U256::from(transaction.gas_limit()), - input: transaction.data().clone(), - v: signature.v(), - // Following Hardhat in always returning `v` instead of `y_parity`. - y_parity: None, - r: signature.r(), - s: signature.s(), - chain_id, - transaction_type: transaction_type.map(u8::from), - access_list, - max_fee_per_gas: transaction.max_fee_per_gas(), - max_priority_fee_per_gas: transaction.max_priority_fee_per_gas().cloned(), - max_fee_per_blob_gas: transaction.max_fee_per_blob_gas().cloned(), - blob_versioned_hashes, - }) -} - -pub fn handle_send_transaction_request( - data: &mut ProviderData, - transaction_request: EthTransactionRequest, -) -> Result<(B256, Vec>), ProviderError> { - validate_send_transaction_request(data, &transaction_request)?; - - let transaction_request = resolve_transaction_request(data, transaction_request)?; - let signed_transaction = data.sign_transaction_request(transaction_request)?; +pub fn handle_send_transaction_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionType + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, + request: ChainSpecT::RpcTransactionRequest, +) -> Result<(B256, Vec>), ProviderError> { + let sender = *request.sender(); + + let context = TransactionContext { data }; + let request = request.resolve_rpc_type(context)?; + + let request = TransactionRequestAndSender { request, sender }; + let signed_transaction = data.sign_transaction_request(request)?; send_raw_transaction_and_log(data, signed_transaction) } -pub fn handle_send_raw_transaction_request( - data: &mut ProviderData, +pub fn handle_send_raw_transaction_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionType + + TransactionValidation< + ValidationError: From + PartialEq, + >, + PooledTransaction: IsEip155, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, raw_transaction: Bytes, -) -> Result<(B256, Vec>), ProviderError> { +) -> Result<(B256, Vec>), ProviderError> { let mut raw_transaction: &[u8] = raw_transaction.as_ref(); let pooled_transaction = - PooledTransaction::decode(&mut raw_transaction).map_err(|err| match err { + ChainSpecT::PooledTransaction::decode(&mut raw_transaction).map_err(|err| match err { edr_eth::rlp::Error::Custom(message) if transaction::Signed::is_invalid_transaction_type_error(message) => { let type_id = *raw_transaction.first().expect("We already validated that the transaction is not empty if it's an invalid transaction type error."); ProviderError::InvalidTransactionType(type_id) @@ -278,149 +218,33 @@ pub fn handle_send_raw_transaction_request( err => ProviderError::InvalidArgument(err.to_string()), })?; - let signed_transaction = pooled_transaction.into_payload(); - validate_send_raw_transaction_request(data, &signed_transaction)?; + validate_send_raw_transaction_request(data, &pooled_transaction)?; + let signed_transaction = pooled_transaction.into(); - let signed_transaction = transaction::validate(signed_transaction, data.spec_id()) + let signed_transaction = transaction::validate(signed_transaction, data.evm_spec_id()) .map_err(ProviderError::TransactionCreationError)?; send_raw_transaction_and_log(data, signed_transaction) } -fn resolve_transaction_request( - data: &mut ProviderData, - transaction_request: EthTransactionRequest, -) -> Result { - const DEFAULT_MAX_PRIORITY_FEE_PER_GAS: u64 = 1_000_000_000; - - /// # Panics - /// - /// Panics if `data.spec_id()` is less than `SpecId::LONDON`. - fn calculate_max_fee_per_gas( - data: &ProviderData, - max_priority_fee_per_gas: U256, - ) -> Result> { - let base_fee_per_gas = data - .next_block_base_fee_per_gas()? - .expect("We already validated that the block is post-London."); - Ok(U256::from(2) * base_fee_per_gas + max_priority_fee_per_gas) - } - - let EthTransactionRequest { - from, - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - data: input, - nonce, - chain_id, - access_list, - // We ignore the transaction type - transaction_type: _transaction_type, - blobs: _blobs, - blob_hashes: _blob_hashes, - } = transaction_request; - - let chain_id = chain_id.unwrap_or_else(|| data.chain_id()); - let gas_limit = gas.unwrap_or_else(|| data.block_gas_limit()); - let input = input.map_or(Bytes::new(), Into::into); - let nonce = nonce.map_or_else(|| data.account_next_nonce(&from), Ok)?; - let value = value.unwrap_or(U256::ZERO); - - let request = match ( - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - access_list, - ) { - (gas_price, max_fee_per_gas, max_priority_fee_per_gas, access_list) - if data.spec_id() >= SpecId::LONDON - && (gas_price.is_none() - || max_fee_per_gas.is_some() - || max_priority_fee_per_gas.is_some()) => - { - let (max_fee_per_gas, max_priority_fee_per_gas) = - match (max_fee_per_gas, max_priority_fee_per_gas) { - (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => { - (max_fee_per_gas, max_priority_fee_per_gas) - } - (Some(max_fee_per_gas), None) => ( - max_fee_per_gas, - max_fee_per_gas.min(U256::from(DEFAULT_MAX_PRIORITY_FEE_PER_GAS)), - ), - (None, Some(max_priority_fee_per_gas)) => { - let max_fee_per_gas = - calculate_max_fee_per_gas(data, max_priority_fee_per_gas)?; - (max_fee_per_gas, max_priority_fee_per_gas) - } - (None, None) => { - let max_priority_fee_per_gas = U256::from(DEFAULT_MAX_PRIORITY_FEE_PER_GAS); - let max_fee_per_gas = - calculate_max_fee_per_gas(data, max_priority_fee_per_gas)?; - (max_fee_per_gas, max_priority_fee_per_gas) - } - }; - - transaction::Request::Eip1559(transaction::request::Eip1559 { - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas_limit, - value, - input, - kind: match to { - Some(to) => TxKind::Call(to), - None => TxKind::Create, - }, - chain_id, - access_list: access_list.unwrap_or_default(), - }) - } - (gas_price, _, _, Some(access_list)) => { - transaction::Request::Eip2930(transaction::request::Eip2930 { - nonce, - gas_price: gas_price.map_or_else(|| data.next_gas_price(), Ok)?, - gas_limit, - value, - input, - kind: match to { - Some(to) => TxKind::Call(to), - None => TxKind::Create, - }, - chain_id, - access_list, - }) - } - (gas_price, _, _, _) => transaction::Request::Eip155(transaction::request::Eip155 { - nonce, - gas_price: gas_price.map_or_else(|| data.next_gas_price(), Ok)?, - gas_limit, - value, - input, - kind: match to { - Some(to) => TxKind::Call(to), - None => TxKind::Create, - }, - chain_id, - }), - }; - - Ok(TransactionRequestAndSender { - request, - sender: from, - }) -} - -fn send_raw_transaction_and_log( - data: &mut ProviderData, - signed_transaction: transaction::Signed, -) -> Result<(B256, Vec>), ProviderError> { +fn send_raw_transaction_and_log< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionType + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, + signed_transaction: ChainSpecT::Transaction, +) -> Result<(B256, Vec>), ProviderError> { let result = data.send_transaction(signed_transaction.clone())?; - let spec_id = data.spec_id(); + let spec_id = data.evm_spec_id(); data.logger_mut() .log_send_transaction(spec_id, &signed_transaction, &result.mining_results) .map_err(ProviderError::Logger)?; @@ -448,55 +272,13 @@ fn send_raw_transaction_and_log( Ok(result.into()) } -fn validate_send_transaction_request( - data: &ProviderData, - request: &EthTransactionRequest, -) -> Result<(), ProviderError> { - if let Some(chain_id) = request.chain_id { - let expected = data.chain_id(); - if chain_id != expected { - return Err(ProviderError::InvalidChainId { - expected, - actual: chain_id, - }); - } - } - - if let Some(request_data) = &request.data { - validate_eip3860_max_initcode_size( - data.spec_id(), - data.allow_unlimited_initcode_size(), - request.to.as_ref(), - request_data, - )?; - } - - if request.blob_hashes.is_some() || request.blobs.is_some() { - return Err(ProviderError::Eip4844TransactionUnsupported); - } - - if let Some(transaction_type) = request.transaction_type { - if transaction_type == u8::from(transaction::Type::Eip4844) { - return Err(ProviderError::Eip4844TransactionUnsupported); - } - } - - validate_transaction_and_call_request(data.spec_id(), request).map_err(|err| match err { - ProviderError::UnsupportedEIP1559Parameters { - minimum_hardfork, .. - } => ProviderError::InvalidArgument(format!("\ -EIP-1559 style fee params (maxFeePerGas or maxPriorityFeePerGas) received but they are not supported by the current hardfork. - -You can use them by running Hardhat Network with 'hardfork' {minimum_hardfork:?} or later. - ")), - err => err, - }) -} - -fn validate_send_raw_transaction_request( - data: &ProviderData, - transaction: &transaction::Signed, -) -> Result<(), ProviderError> { +fn validate_send_raw_transaction_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, + transaction: &ChainSpecT::PooledTransaction, +) -> Result<(), ProviderError> { if let Some(tx_chain_id) = transaction.chain_id() { let expected = data.chain_id(); if tx_chain_id != expected { @@ -510,29 +292,32 @@ fn validate_send_raw_transaction_request( } validate_eip3860_max_initcode_size( - data.spec_id(), + data.evm_spec_id(), data.allow_unlimited_initcode_size(), transaction.kind().to(), transaction.data(), )?; - validate_transaction_and_call_request(data.spec_id(), transaction).map_err(|err| match err { - ProviderError::UnsupportedEIP1559Parameters { - minimum_hardfork, .. - } => ProviderError::InvalidArgument(format!( - "\ + validate_transaction_and_call_request(data.evm_spec_id(), transaction).map_err( + |err| match err { + ProviderError::UnsupportedEIP1559Parameters { + minimum_hardfork, .. + } => ProviderError::InvalidArgument(format!( + "\ Trying to send an EIP-1559 transaction but they are not supported by the current hard fork.\ \ You can use them by running Hardhat Network with 'hardfork' {minimum_hardfork:?} or later." - )), - err => err, - }) + )), + err => err, + }, + ) } #[cfg(test)] mod tests { use anyhow::Context; use edr_eth::{Address, Bytes, U256}; + use transaction::{signed::FakeSign as _, TxKind}; use super::*; use crate::{data::test_utils::ProviderTestFixture, test_utils::one_ether}; @@ -562,7 +347,7 @@ mod tests { chain_id, }) .fake_sign(impersonated_account); - let transaction = transaction::validate(transaction, fixture.provider_data.spec_id())?; + let transaction = transaction::validate(transaction, fixture.provider_data.evm_spec_id())?; fixture.provider_data.set_auto_mining(true); let result = fixture.provider_data.send_transaction(transaction)?; diff --git a/crates/edr_provider/src/requests/eth/web3.rs b/crates/edr_provider/src/requests/eth/web3.rs index 8b6ef0974..d9018fb9e 100644 --- a/crates/edr_provider/src/requests/eth/web3.rs +++ b/crates/edr_provider/src/requests/eth/web3.rs @@ -1,4 +1,7 @@ +use core::fmt::Debug; + use edr_eth::{Bytes, B256}; +use edr_evm::chain_spec::ChainSpec; use sha3::{Digest, Keccak256}; use crate::ProviderError; @@ -11,11 +14,14 @@ pub fn client_version() -> String { ) } -pub fn handle_web3_client_version_request() -> Result { +pub fn handle_web3_client_version_request>( +) -> Result> { Ok(client_version()) } -pub fn handle_web3_sha3_request(message: Bytes) -> Result { +pub fn handle_web3_sha3_request>( + message: Bytes, +) -> Result> { let hash = Keccak256::digest(&message[..]); Ok(B256::from_slice(&hash[..])) } diff --git a/crates/edr_provider/src/requests/hardhat/accounts.rs b/crates/edr_provider/src/requests/hardhat/accounts.rs index 1439f8df7..486678cb9 100644 --- a/crates/edr_provider/src/requests/hardhat/accounts.rs +++ b/crates/edr_provider/src/requests/hardhat/accounts.rs @@ -1,19 +1,25 @@ use edr_eth::Address; -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; +use crate::{data::ProviderData, spec::ProviderSpec, time::TimeSinceEpoch, ProviderError}; -pub fn handle_impersonate_account_request( - data: &mut ProviderData, +pub fn handle_impersonate_account_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, address: Address, -) -> Result { +) -> Result> { data.impersonate_account(address); Ok(true) } -pub fn handle_stop_impersonating_account_request( - data: &mut ProviderData, +pub fn handle_stop_impersonating_account_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, address: Address, -) -> Result { +) -> Result> { Ok(data.stop_impersonating_account(address)) } diff --git a/crates/edr_provider/src/requests/hardhat/config.rs b/crates/edr_provider/src/requests/hardhat/config.rs index 6e3353522..c9e8c7e15 100644 --- a/crates/edr_provider/src/requests/hardhat/config.rs +++ b/crates/edr_provider/src/requests/hardhat/config.rs @@ -3,19 +3,26 @@ use edr_eth::{Address, B256, U256}; use crate::{ data::ProviderData, requests::{eth::client_version, hardhat::rpc_types::Metadata}, + spec::{ProviderSpec, SyncProviderSpec}, time::TimeSinceEpoch, ProviderError, }; -pub fn handle_get_automine_request( - data: &ProviderData, -) -> Result { +pub fn handle_get_automine_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, +) -> Result> { Ok(data.is_auto_mining()) } -pub fn handle_metadata_request( - data: &ProviderData, -) -> Result { +pub fn handle_metadata_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, +) -> Result> { Ok(Metadata { client_version: client_version(), chain_id: data.chain_id(), @@ -26,37 +33,49 @@ pub fn handle_metadata_request( }) } -pub fn handle_set_coinbase_request( - data: &mut ProviderData, +pub fn handle_set_coinbase_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, coinbase: Address, -) -> Result { +) -> Result> { data.set_coinbase(coinbase); Ok(true) } -pub fn handle_set_min_gas_price( - data: &mut ProviderData, +pub fn handle_set_min_gas_price< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, min_gas_price: U256, -) -> Result { +) -> Result> { data.set_min_gas_price(min_gas_price)?; Ok(true) } -pub fn handle_set_next_block_base_fee_per_gas_request( - data: &mut ProviderData, +pub fn handle_set_next_block_base_fee_per_gas_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, base_fee_per_gas: U256, -) -> Result { +) -> Result> { data.set_next_block_base_fee_per_gas(base_fee_per_gas)?; Ok(true) } -pub fn handle_set_prev_randao_request( - data: &mut ProviderData, +pub fn handle_set_prev_randao_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, prev_randao: B256, -) -> Result { +) -> Result> { data.set_next_prev_randao(prev_randao)?; Ok(true) diff --git a/crates/edr_provider/src/requests/hardhat/log.rs b/crates/edr_provider/src/requests/hardhat/log.rs index f9c61bac9..18d9dc959 100644 --- a/crates/edr_provider/src/requests/hardhat/log.rs +++ b/crates/edr_provider/src/requests/hardhat/log.rs @@ -1,9 +1,12 @@ -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; +use crate::{data::ProviderData, spec::ProviderSpec, time::TimeSinceEpoch, ProviderError}; -pub fn handle_set_logging_enabled_request( - data: &mut ProviderData, +pub fn handle_set_logging_enabled_request< + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, is_enabled: bool, -) -> Result { +) -> Result> { data.logger_mut().set_is_enabled(is_enabled); Ok(true) } diff --git a/crates/edr_provider/src/requests/hardhat/miner.rs b/crates/edr_provider/src/requests/hardhat/miner.rs index be6e88e20..cfd3de895 100644 --- a/crates/edr_provider/src/requests/hardhat/miner.rs +++ b/crates/edr_provider/src/requests/hardhat/miner.rs @@ -1,25 +1,45 @@ -use edr_eth::chain_spec::L1ChainSpec; +use edr_eth::{result::InvalidTransaction, transaction::TransactionValidation}; use edr_evm::trace::Trace; -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; +use crate::{data::ProviderData, spec::SyncProviderSpec, time::TimeSinceEpoch, ProviderError}; -pub fn handle_interval_mine_request( - data: &mut ProviderData, -) -> Result { +pub fn handle_interval_mine_request< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, +) -> Result> { data.interval_mine() } -pub fn handle_mine( - data: &mut ProviderData, +pub fn handle_mine< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, number_of_blocks: Option, interval: Option, -) -> Result<(bool, Vec>), ProviderError> { +) -> Result<(bool, Vec>), ProviderError> { let number_of_blocks = number_of_blocks.unwrap_or(1); let interval = interval.unwrap_or(1); let mined_block_results = data.mine_and_commit_blocks(number_of_blocks, interval)?; - let spec_id = data.spec_id(); + let spec_id = data.evm_spec_id(); data.logger_mut() .log_mined_block(spec_id, &mined_block_results) .map_err(ProviderError::Logger)?; diff --git a/crates/edr_provider/src/requests/hardhat/state.rs b/crates/edr_provider/src/requests/hardhat/state.rs index af6b80a95..01edd2a71 100644 --- a/crates/edr_provider/src/requests/hardhat/state.rs +++ b/crates/edr_provider/src/requests/hardhat/state.rs @@ -1,43 +1,46 @@ use edr_eth::{Address, Bytes, U256}; -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; +use crate::{data::ProviderData, spec::SyncProviderSpec, time::TimeSinceEpoch, ProviderError}; -pub fn handle_set_balance( - data: &mut ProviderData, +pub fn handle_set_balance, TimerT: Clone + TimeSinceEpoch>( + data: &mut ProviderData, address: Address, balance: U256, -) -> Result { +) -> Result> { data.set_balance(address, balance)?; Ok(true) } -pub fn handle_set_code( - data: &mut ProviderData, +pub fn handle_set_code, TimerT: Clone + TimeSinceEpoch>( + data: &mut ProviderData, address: Address, code: Bytes, -) -> Result { +) -> Result> { data.set_code(address, code)?; Ok(true) } -pub fn handle_set_nonce( - data: &mut ProviderData, +pub fn handle_set_nonce, TimerT: Clone + TimeSinceEpoch>( + data: &mut ProviderData, address: Address, nonce: u64, -) -> Result { +) -> Result> { data.set_nonce(address, nonce)?; Ok(true) } -pub fn handle_set_storage_at( - data: &mut ProviderData, +pub fn handle_set_storage_at< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, address: Address, index: U256, value: U256, -) -> Result { +) -> Result> { data.set_account_storage_slot(address, index, value)?; Ok(true) diff --git a/crates/edr_provider/src/requests/hardhat/transactions.rs b/crates/edr_provider/src/requests/hardhat/transactions.rs index 751d1048e..b2d58fa22 100644 --- a/crates/edr_provider/src/requests/hardhat/transactions.rs +++ b/crates/edr_provider/src/requests/hardhat/transactions.rs @@ -1,11 +1,14 @@ use edr_eth::B256; -use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError}; +use crate::{data::ProviderData, spec::SyncProviderSpec, time::TimeSinceEpoch, ProviderError}; -pub fn handle_drop_transaction( - data: &mut ProviderData, +pub fn handle_drop_transaction< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, transaction_hash: B256, -) -> Result { +) -> Result> { let was_removed = data.remove_pending_transaction(&transaction_hash).is_some(); if was_removed { return Ok(true); diff --git a/crates/edr_provider/src/requests/methods.rs b/crates/edr_provider/src/requests/methods.rs index bf60eafc0..77134c546 100644 --- a/crates/edr_provider/src/requests/methods.rs +++ b/crates/edr_provider/src/requests/methods.rs @@ -1,11 +1,12 @@ use alloy_dyn_abi::eip712::TypedData; +use derive_where::derive_where; use edr_eth::{ filter::{LogFilterOptions, SubscriptionType}, serde::{optional_single_to_sequence, sequence_to_optional_single}, - transaction::EthTransactionRequest, Address, BlockSpec, Bytes, PreEip1898BlockSpec, B256, U256, U64, }; -use edr_rpc_eth::{CallRequest, StateOverrideOptions}; +use edr_rpc_eth::{spec::RpcSpec, StateOverrideOptions}; +use serde::{Deserialize, Serialize}; use super::serde::{RpcAddress, Timestamp}; use crate::requests::{ @@ -26,9 +27,10 @@ mod optional_block_spec { } /// for an invoking a method on a remote ethereum node -#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(tag = "method", content = "params")] -pub enum MethodInvocation { +#[derive(Deserialize, Serialize)] +#[derive_where(Clone, Debug, PartialEq; ChainSpecT::RpcCallRequest, ChainSpecT::RpcTransactionRequest)] +#[serde(bound = "", tag = "method", content = "params")] +pub enum MethodInvocation { /// `eth_accounts` #[serde(rename = "eth_accounts", with = "edr_eth::serde::empty_params")] Accounts(()), @@ -41,7 +43,7 @@ pub enum MethodInvocation { /// `eth_call` #[serde(rename = "eth_call")] Call( - CallRequest, + ChainSpecT::RpcCallRequest, #[serde( skip_serializing_if = "Option::is_none", default = "optional_block_spec::latest" @@ -58,7 +60,7 @@ pub enum MethodInvocation { /// `eth_estimateGas` #[serde(rename = "eth_estimateGas")] EstimateGas( - CallRequest, + ChainSpecT::RpcCallRequest, #[serde( skip_serializing_if = "Option::is_none", default = "optional_block_spec::pending" @@ -220,7 +222,7 @@ pub enum MethodInvocation { SendRawTransaction(Bytes), /// `eth_sendTransaction` #[serde(rename = "eth_sendTransaction", with = "edr_eth::serde::sequence")] - SendTransaction(EthTransactionRequest), + SendTransaction(ChainSpecT::RpcTransactionRequest), /// `personal_sign` #[serde(rename = "personal_sign")] PersonalSign( @@ -289,7 +291,7 @@ pub enum MethodInvocation { // `debug_traceTransaction` #[serde(rename = "debug_traceCall")] DebugTraceCall( - CallRequest, + ChainSpecT::RpcCallRequest, #[serde(default)] Option, #[serde(default)] Option, ), @@ -408,7 +410,7 @@ pub enum MethodInvocation { StopImpersonatingAccount(RpcAddress), } -impl MethodInvocation { +impl MethodInvocation { /// Retrieves the instance's method name. pub fn method_name(&self) -> &'static str { match self { diff --git a/crates/edr_provider/src/requests/resolve.rs b/crates/edr_provider/src/requests/resolve.rs new file mode 100644 index 000000000..f65ed187d --- /dev/null +++ b/crates/edr_provider/src/requests/resolve.rs @@ -0,0 +1,303 @@ +use edr_eth::{chain_spec::L1ChainSpec, Bytes, SpecId, U256}; +use edr_evm::{blockchain::BlockchainError, transaction}; +use edr_rpc_eth::{CallRequest, TransactionRequest}; + +use super::validation::validate_call_request; +use crate::{ + data::ProviderData, + requests::validation::validate_send_transaction_request, + spec::{CallContext, ResolveRpcType, TransactionContext}, + time::TimeSinceEpoch, + ProviderError, +}; + +impl ResolveRpcType for CallRequest { + type Context<'context> = CallContext<'context, L1ChainSpec, TimerT>; + + type Error = ProviderError; + + fn resolve_rpc_type( + self, + context: Self::Context<'_>, + ) -> Result> { + let CallContext { + data, + block_spec, + state_overrides, + default_gas_price_fn, + max_fees_fn, + } = context; + + validate_call_request(data.evm_spec_id(), &self, block_spec)?; + + let CallRequest { + from, + to, + gas, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + value, + data: input, + access_list, + .. + } = self; + + let chain_id = data.chain_id(); + let sender = from.unwrap_or_else(|| data.default_caller()); + let gas_limit = gas.unwrap_or_else(|| data.block_gas_limit()); + let input = input.map_or(Bytes::new(), Bytes::from); + let nonce = data.nonce(&sender, Some(block_spec), state_overrides)?; + let value = value.unwrap_or(U256::ZERO); + + let evm_spec_id = data.evm_spec_id(); + let request = if evm_spec_id < SpecId::LONDON || gas_price.is_some() { + let gas_price = gas_price.map_or_else(|| default_gas_price_fn(data), Ok)?; + match access_list { + Some(access_list) if evm_spec_id >= SpecId::BERLIN => { + transaction::Request::Eip2930(transaction::request::Eip2930 { + nonce, + gas_price, + gas_limit, + value, + input, + kind: to.into(), + chain_id, + access_list, + }) + } + _ => transaction::Request::Eip155(transaction::request::Eip155 { + nonce, + gas_price, + gas_limit, + kind: to.into(), + value, + input, + chain_id, + }), + } + } else { + let (max_fee_per_gas, max_priority_fee_per_gas) = + max_fees_fn(data, block_spec, max_fee_per_gas, max_priority_fee_per_gas)?; + + transaction::Request::Eip1559(transaction::request::Eip1559 { + chain_id, + nonce, + max_fee_per_gas, + max_priority_fee_per_gas, + gas_limit, + kind: to.into(), + value, + input, + access_list: access_list.unwrap_or_default(), + }) + }; + + Ok(request) + } +} + +impl ResolveRpcType + for TransactionRequest +{ + type Context<'context> = TransactionContext<'context, L1ChainSpec, TimerT>; + + type Error = ProviderError; + + fn resolve_rpc_type( + self, + context: Self::Context<'_>, + ) -> Result> { + const DEFAULT_MAX_PRIORITY_FEE_PER_GAS: u64 = 1_000_000_000; + + /// # Panics + /// + /// Panics if `data.evm_spec_id()` is less than `SpecId::LONDON`. + fn calculate_max_fee_per_gas( + data: &ProviderData, + max_priority_fee_per_gas: U256, + ) -> Result> { + let base_fee_per_gas = data + .next_block_base_fee_per_gas()? + .expect("We already validated that the block is post-London."); + Ok(U256::from(2) * base_fee_per_gas + max_priority_fee_per_gas) + } + + let TransactionContext { data } = context; + + validate_send_transaction_request(data, &self)?; + + let TransactionRequest { + from, + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + value, + data: input, + nonce, + chain_id, + access_list, + // We ignore the transaction type + transaction_type: _transaction_type, + blobs: _blobs, + blob_hashes: _blob_hashes, + } = self; + + let chain_id = chain_id.unwrap_or_else(|| data.chain_id()); + let gas_limit = gas.unwrap_or_else(|| data.block_gas_limit()); + let input = input.map_or(Bytes::new(), Into::into); + let nonce = nonce.map_or_else(|| data.account_next_nonce(&from), Ok)?; + let value = value.unwrap_or(U256::ZERO); + + let request = match ( + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + access_list, + ) { + (gas_price, max_fee_per_gas, max_priority_fee_per_gas, access_list) + if data.evm_spec_id() >= SpecId::LONDON + && (gas_price.is_none() + || max_fee_per_gas.is_some() + || max_priority_fee_per_gas.is_some()) => + { + let (max_fee_per_gas, max_priority_fee_per_gas) = + match (max_fee_per_gas, max_priority_fee_per_gas) { + (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => { + (max_fee_per_gas, max_priority_fee_per_gas) + } + (Some(max_fee_per_gas), None) => ( + max_fee_per_gas, + max_fee_per_gas.min(U256::from(DEFAULT_MAX_PRIORITY_FEE_PER_GAS)), + ), + (None, Some(max_priority_fee_per_gas)) => { + let max_fee_per_gas = + calculate_max_fee_per_gas(data, max_priority_fee_per_gas)?; + (max_fee_per_gas, max_priority_fee_per_gas) + } + (None, None) => { + let max_priority_fee_per_gas = + U256::from(DEFAULT_MAX_PRIORITY_FEE_PER_GAS); + let max_fee_per_gas = + calculate_max_fee_per_gas(data, max_priority_fee_per_gas)?; + (max_fee_per_gas, max_priority_fee_per_gas) + } + }; + + transaction::Request::Eip1559(transaction::request::Eip1559 { + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + value, + input, + kind: to.into(), + chain_id, + access_list: access_list.unwrap_or_default(), + }) + } + (gas_price, _, _, Some(access_list)) => { + transaction::Request::Eip2930(transaction::request::Eip2930 { + nonce, + gas_price: gas_price.map_or_else(|| data.next_gas_price(), Ok)?, + gas_limit, + value, + input, + kind: to.into(), + chain_id, + access_list, + }) + } + (gas_price, _, _, _) => transaction::Request::Eip155(transaction::request::Eip155 { + nonce, + gas_price: gas_price.map_or_else(|| data.next_gas_price(), Ok)?, + gas_limit, + value, + input, + kind: to.into(), + chain_id, + }), + }; + + Ok(request) + } +} + +#[cfg(test)] +mod tests { + use edr_eth::BlockSpec; + use edr_evm::state::StateOverrides; + use edr_rpc_eth::CallRequest; + + use super::*; + use crate::{data::test_utils::ProviderTestFixture, test_utils::pending_base_fee}; + + #[test] + fn resolve_call_request_with_gas_price() -> anyhow::Result<()> { + let mut fixture = ProviderTestFixture::new_local()?; + + let pending_base_fee = pending_base_fee(&mut fixture.provider_data)?; + + let request = CallRequest { + from: Some(fixture.nth_local_account(0)?), + to: Some(fixture.nth_local_account(1)?), + gas_price: Some(pending_base_fee), + ..CallRequest::default() + }; + + let context = CallContext { + data: &mut fixture.provider_data, + block_spec: &BlockSpec::pending(), + state_overrides: &StateOverrides::default(), + default_gas_price_fn: |_data| unreachable!("gas_price is set"), + max_fees_fn: |_, _, _, _| unreachable!("gas_price is set"), + }; + + let resolved = request.resolve_rpc_type(context)?; + + assert_eq!(*resolved.gas_price(), pending_base_fee); + + Ok(()) + } + + #[test] + fn resolve_call_request_inner_with_max_fee_and_max_priority_fee() -> anyhow::Result<()> { + let mut fixture = ProviderTestFixture::new_local()?; + + let max_fee_per_gas = pending_base_fee(&mut fixture.provider_data)?; + let max_priority_fee_per_gas = Some(max_fee_per_gas / U256::from(2)); + + let request = CallRequest { + from: Some(fixture.nth_local_account(0)?), + to: Some(fixture.nth_local_account(1)?), + max_fee_per_gas: Some(max_fee_per_gas), + max_priority_fee_per_gas, + ..CallRequest::default() + }; + + let context = CallContext { + data: &mut fixture.provider_data, + block_spec: &BlockSpec::pending(), + state_overrides: &StateOverrides::default(), + default_gas_price_fn: |_data| unreachable!("max fees are set"), + max_fees_fn: |_data, _block_spec, max_fee_per_gas, max_priority_fee_per_gas| { + Ok(( + max_fee_per_gas.expect("max fee is set"), + max_priority_fee_per_gas.expect("max priority fee is set"), + )) + }, + }; + + let resolved = request.resolve_rpc_type(context)?; + + assert_eq!(*resolved.gas_price(), max_fee_per_gas); + assert_eq!( + resolved.max_priority_fee_per_gas().cloned(), + max_priority_fee_per_gas + ); + + Ok(()) + } +} diff --git a/crates/edr_provider/src/requests/serde.rs b/crates/edr_provider/src/requests/serde.rs index 25ecbdea9..bc130b29a 100644 --- a/crates/edr_provider/src/requests/serde.rs +++ b/crates/edr_provider/src/requests/serde.rs @@ -6,6 +6,7 @@ use std::{ use alloy_dyn_abi::TypedData; use edr_eth::{Address, Bytes, U256, U64}; +use edr_evm::chain_spec::ChainSpec; use serde::{Deserialize, Deserializer, Serialize}; use crate::ProviderError; @@ -103,7 +104,9 @@ impl<'a> InvalidRequestReason<'a> { } /// Converts the invalid request reason into a provider error. - pub fn provider_error(&self) -> Option<(String, ProviderError)> { + pub fn provider_error>( + &self, + ) -> Option<(String, ProviderError)> { match self { InvalidRequestReason::InvalidJson { .. } => None, InvalidRequestReason::InvalidStorageKey { diff --git a/crates/edr_provider/src/requests/validation.rs b/crates/edr_provider/src/requests/validation.rs index 068e101d4..ce38593af 100644 --- a/crates/edr_provider/src/requests/validation.rs +++ b/crates/edr_provider/src/requests/validation.rs @@ -1,125 +1,188 @@ +use core::fmt::Debug; + use edr_eth::{ - transaction::{self, EthTransactionRequest}, - AccessListItem, Address, BlockSpec, BlockTag, Bytes, PreEip1898BlockSpec, SpecId, B256, + transaction::{pooled::PooledTransaction, ExecutableTransaction}, + AccessListItem, Address, Blob, BlockSpec, BlockTag, Bytes, PreEip1898BlockSpec, SpecId, B256, MAX_INITCODE_SIZE, U256, }; -use edr_rpc_eth::CallRequest; - -use crate::ProviderError; - -/// Data used for validating a transaction complies with a [`SpecId`]. -pub struct SpecValidationData<'data> { - pub to: Option<&'data Address>, - pub gas_price: Option<&'data U256>, - pub max_fee_per_gas: Option<&'data U256>, - pub max_priority_fee_per_gas: Option<&'data U256>, - pub access_list: Option<&'data Vec>, - pub blobs: Option<&'data Vec>, - pub blob_hashes: Option<&'data Vec>, +use edr_evm::{ + chain_spec::ChainSpec, + transaction::{self, Transaction}, +}; +use edr_rpc_eth::{CallRequest, TransactionRequest}; + +use crate::{ + data::ProviderData, spec::HardforkValidationData, time::TimeSinceEpoch, ProviderError, + SyncProviderSpec, +}; + +impl HardforkValidationData for TransactionRequest { + fn to(&self) -> Option<&Address> { + self.to.as_ref() + } + + fn gas_price(&self) -> Option<&U256> { + self.gas_price.as_ref() + } + + fn max_fee_per_gas(&self) -> Option<&U256> { + self.max_fee_per_gas.as_ref() + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + self.max_priority_fee_per_gas.as_ref() + } + + fn access_list(&self) -> Option<&Vec> { + self.access_list.as_ref() + } + + fn blobs(&self) -> Option<&Vec> { + self.blobs.as_ref() + } + + fn blob_hashes(&self) -> Option<&Vec> { + self.blob_hashes.as_ref() + } } -impl<'data> From<&'data EthTransactionRequest> for SpecValidationData<'data> { - fn from(value: &'data EthTransactionRequest) -> Self { - Self { - to: value.to.as_ref(), - gas_price: value.gas_price.as_ref(), - max_fee_per_gas: value.max_fee_per_gas.as_ref(), - max_priority_fee_per_gas: value.max_priority_fee_per_gas.as_ref(), - access_list: value.access_list.as_ref(), - blobs: value.blobs.as_ref(), - blob_hashes: value.blob_hashes.as_ref(), - } +impl HardforkValidationData for CallRequest { + fn to(&self) -> Option<&Address> { + self.to.as_ref() + } + + fn gas_price(&self) -> Option<&U256> { + self.gas_price.as_ref() + } + + fn max_fee_per_gas(&self) -> Option<&U256> { + self.max_fee_per_gas.as_ref() + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + self.max_priority_fee_per_gas.as_ref() + } + + fn access_list(&self) -> Option<&Vec> { + self.access_list.as_ref() + } + + fn blobs(&self) -> Option<&Vec> { + self.blobs.as_ref() + } + + fn blob_hashes(&self) -> Option<&Vec> { + self.blob_hashes.as_ref() } } -impl<'data> From<&'data CallRequest> for SpecValidationData<'data> { - fn from(value: &'data CallRequest) -> Self { - Self { - to: value.to.as_ref(), - gas_price: value.gas_price.as_ref(), - max_fee_per_gas: value.max_fee_per_gas.as_ref(), - max_priority_fee_per_gas: value.max_priority_fee_per_gas.as_ref(), - access_list: value.access_list.as_ref(), - blobs: value.blobs.as_ref(), - blob_hashes: value.blob_hashes.as_ref(), +impl HardforkValidationData for PooledTransaction { + fn to(&self) -> Option<&Address> { + Some(self.caller()) + } + + fn gas_price(&self) -> Option<&U256> { + match self { + PooledTransaction::PreEip155Legacy(tx) => Some(&tx.gas_price), + PooledTransaction::PostEip155Legacy(tx) => Some(&tx.gas_price), + PooledTransaction::Eip2930(tx) => Some(&tx.gas_price), + PooledTransaction::Eip1559(_) | PooledTransaction::Eip4844(_) => None, + } + } + + fn max_fee_per_gas(&self) -> Option<&U256> { + ExecutableTransaction::max_fee_per_gas(self) + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + Transaction::max_priority_fee_per_gas(self) + } + + fn access_list(&self) -> Option<&Vec> { + match self { + PooledTransaction::PreEip155Legacy(_) | PooledTransaction::PostEip155Legacy(_) => None, + PooledTransaction::Eip2930(tx) => Some(tx.access_list.0.as_ref()), + PooledTransaction::Eip1559(tx) => Some(tx.access_list.0.as_ref()), + PooledTransaction::Eip4844(tx) => Some(&tx.payload().access_list), + } + } + + fn blobs(&self) -> Option<&Vec> { + match self { + PooledTransaction::Eip4844(tx) => Some(tx.blobs_ref()), + _ => None, + } + } + + fn blob_hashes(&self) -> Option<&Vec> { + match self { + PooledTransaction::Eip4844(tx) => Some(&tx.payload().blob_hashes), + _ => None, } } } -impl<'data> From<&'data transaction::Signed> for SpecValidationData<'data> { - fn from(value: &'data transaction::Signed) -> Self { - match value { - transaction::Signed::PreEip155Legacy(tx) => Self { - to: tx.kind.to(), - gas_price: Some(&tx.gas_price), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - access_list: None, - blobs: None, - blob_hashes: None, - }, - transaction::Signed::PostEip155Legacy(tx) => Self { - to: tx.kind.to(), - gas_price: Some(&tx.gas_price), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - access_list: None, - blobs: None, - blob_hashes: None, - }, - transaction::Signed::Eip2930(tx) => Self { - to: tx.kind.to(), - gas_price: Some(&tx.gas_price), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - access_list: Some(tx.access_list.0.as_ref()), - blobs: None, - blob_hashes: None, - }, - transaction::Signed::Eip1559(tx) => Self { - to: tx.kind.to(), - gas_price: None, - max_fee_per_gas: Some(&tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(&tx.max_priority_fee_per_gas), - access_list: Some(tx.access_list.0.as_ref()), - blobs: None, - blob_hashes: None, - }, - transaction::Signed::Eip4844(tx) => Self { - to: Some(&tx.to), - gas_price: None, - max_fee_per_gas: Some(&tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(&tx.max_priority_fee_per_gas), - access_list: Some(tx.access_list.0.as_ref()), - blobs: None, - blob_hashes: Some(tx.blob_hashes.as_ref()), - }, +pub(crate) fn validate_send_transaction_request< + ChainSpecT: SyncProviderSpec, + TimerT: Clone + TimeSinceEpoch, +>( + data: &ProviderData, + request: &TransactionRequest, +) -> Result<(), ProviderError> { + if let Some(chain_id) = request.chain_id { + let expected = data.chain_id(); + if chain_id != expected { + return Err(ProviderError::InvalidChainId { + expected, + actual: chain_id, + }); + } + } + + if let Some(request_data) = &request.data { + validate_eip3860_max_initcode_size( + data.evm_spec_id(), + data.allow_unlimited_initcode_size(), + request.to.as_ref(), + request_data, + )?; + } + + if request.blob_hashes.is_some() || request.blobs.is_some() { + return Err(ProviderError::Eip4844TransactionUnsupported); + } + + if let Some(transaction_type) = request.transaction_type { + if transaction_type == u8::from(transaction::Type::Eip4844) { + return Err(ProviderError::Eip4844TransactionUnsupported); } } + + validate_transaction_and_call_request(data.evm_spec_id(), request).map_err(|err| match err { + ProviderError::UnsupportedEIP1559Parameters { + minimum_hardfork, .. + } => ProviderError::InvalidArgument(format!("\ +EIP-1559 style fee params (maxFeePerGas or maxPriorityFeePerGas) received but they are not supported by the current hardfork. + +You can use them by running Hardhat Network with 'hardfork' {minimum_hardfork:?} or later. + ")), + err => err, + }) } -fn validate_transaction_spec( +fn validate_transaction_spec>( spec_id: SpecId, - data: SpecValidationData<'_>, -) -> Result<(), ProviderError> { - let SpecValidationData { - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - access_list, - blobs, - blob_hashes, - } = data; - - if spec_id < SpecId::BERLIN && access_list.is_some() { + value: &impl HardforkValidationData, +) -> Result<(), ProviderError> { + if spec_id < SpecId::BERLIN && value.access_list().is_some() { return Err(ProviderError::UnsupportedAccessListParameter { current_hardfork: spec_id, minimum_hardfork: SpecId::BERLIN, }); } - if spec_id < SpecId::LONDON && (max_fee_per_gas.is_some() || max_priority_fee_per_gas.is_some()) + if spec_id < SpecId::LONDON + && (value.max_fee_per_gas().is_some() || value.max_priority_fee_per_gas().is_some()) { return Err(ProviderError::UnsupportedEIP1559Parameters { current_hardfork: spec_id, @@ -127,41 +190,41 @@ fn validate_transaction_spec( }); } - if spec_id < SpecId::CANCUN && (blobs.is_some() || blob_hashes.is_some()) { + if spec_id < SpecId::CANCUN && (value.blobs().is_some() || value.blob_hashes().is_some()) { return Err(ProviderError::UnsupportedEIP4844Parameters { current_hardfork: spec_id, minimum_hardfork: SpecId::CANCUN, }); } - if gas_price.is_some() { - if max_fee_per_gas.is_some() { + if value.gas_price().is_some() { + if value.max_fee_per_gas().is_some() { return Err(ProviderError::InvalidTransactionInput( "Cannot send both gasPrice and maxFeePerGas params".to_string(), )); } - if max_priority_fee_per_gas.is_some() { + if value.max_priority_fee_per_gas().is_some() { return Err(ProviderError::InvalidTransactionInput( "Cannot send both gasPrice and maxPriorityFeePerGas".to_string(), )); } - if blobs.is_some() { + if value.blobs().is_some() { return Err(ProviderError::InvalidTransactionInput( "Cannot send both gasPrice and blobs".to_string(), )); } - if blob_hashes.is_some() { + if value.blob_hashes().is_some() { return Err(ProviderError::InvalidTransactionInput( "Cannot send both gasPrice and blobHashes".to_string(), )); } } - if let Some(max_fee_per_gas) = max_fee_per_gas { - if let Some(max_priority_fee_per_gas) = max_priority_fee_per_gas { + if let Some(max_fee_per_gas) = value.max_fee_per_gas() { + if let Some(max_priority_fee_per_gas) = value.max_priority_fee_per_gas() { if max_priority_fee_per_gas > max_fee_per_gas { return Err(ProviderError::InvalidTransactionInput(format!( "maxPriorityFeePerGas ({max_priority_fee_per_gas}) is bigger than maxFeePerGas ({max_fee_per_gas})"), @@ -170,18 +233,18 @@ fn validate_transaction_spec( } } - if (blobs.is_some() || blob_hashes.is_some()) && to.is_none() { + if (value.blobs().is_some() || value.blob_hashes().is_some()) && value.to().is_none() { return Err(ProviderError::Eip4844TransactionMissingReceiver); } Ok(()) } -pub fn validate_call_request( +pub fn validate_call_request>( spec_id: SpecId, call_request: &CallRequest, block_spec: &BlockSpec, -) -> Result<(), ProviderError> { +) -> Result<(), ProviderError> { validate_post_merge_block_tags(spec_id, block_spec)?; if call_request.blobs.is_some() | call_request.blob_hashes.is_some() { @@ -203,11 +266,11 @@ You can use them by running Hardhat Network with 'hardfork' {minimum_hardfork:?} }) } -pub fn validate_transaction_and_call_request<'a>( +pub fn validate_transaction_and_call_request>( spec_id: SpecId, - validation_data: impl Into>, -) -> Result<(), ProviderError> { - validate_transaction_spec(spec_id, validation_data.into()).map_err(|err| match err { + validation_data: &impl HardforkValidationData, +) -> Result<(), ProviderError> { + validate_transaction_spec(spec_id, validation_data).map_err(|err| match err { ProviderError::UnsupportedAccessListParameter { minimum_hardfork, .. } => ProviderError::InvalidArgument(format!( @@ -221,12 +284,12 @@ You can use them by running Hardhat Network with 'hardfork' {minimum_hardfork:?} }) } -pub fn validate_eip3860_max_initcode_size( +pub fn validate_eip3860_max_initcode_size>( spec_id: SpecId, allow_unlimited_contract_code_size: bool, to: Option<&Address>, data: &Bytes, -) -> Result<(), ProviderError> { +) -> Result<(), ProviderError> { if spec_id < SpecId::SHANGHAI || to.is_some() || allow_unlimited_contract_code_size { return Ok(()); } @@ -272,10 +335,10 @@ impl<'a> From> for BlockSpec { } } -pub fn validate_post_merge_block_tags<'a>( +pub fn validate_post_merge_block_tags<'a, ChainSpecT: ChainSpec>( hardfork: SpecId, block_spec: impl Into>, -) -> Result<(), ProviderError> { +) -> Result<(), ProviderError> { let block_spec: ValidationBlockSpec<'a> = block_spec.into(); if hardfork < SpecId::MERGE { @@ -299,90 +362,92 @@ pub fn validate_post_merge_block_tags<'a>( #[cfg(test)] mod tests { + use edr_eth::chain_spec::L1ChainSpec; + use super::*; fn assert_mixed_eip_1559_parameters(spec: SpecId) { - let mixed_request = EthTransactionRequest { + let mixed_request = TransactionRequest { from: Address::ZERO, gas_price: Some(U256::ZERO), max_fee_per_gas: Some(U256::ZERO), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(spec, (&mixed_request).into()), + validate_transaction_spec::(spec, &mixed_request), Err(ProviderError::InvalidTransactionInput(_)) )); - let mixed_request = EthTransactionRequest { + let mixed_request = TransactionRequest { from: Address::ZERO, gas_price: Some(U256::ZERO), max_priority_fee_per_gas: Some(U256::ZERO), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(spec, (&mixed_request).into()), + validate_transaction_spec::(spec, &mixed_request), Err(ProviderError::InvalidTransactionInput(_)) )); - let request_with_too_low_max_fee = EthTransactionRequest { + let request_with_too_low_max_fee = TransactionRequest { from: Address::ZERO, max_fee_per_gas: Some(U256::ZERO), max_priority_fee_per_gas: Some(U256::from(1u64)), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(spec, (&request_with_too_low_max_fee).into()), + validate_transaction_spec::(spec, &request_with_too_low_max_fee), Err(ProviderError::InvalidTransactionInput(_)) )); } fn assert_unsupported_eip_1559_parameters(spec: SpecId) { - let eip_1559_request = EthTransactionRequest { + let eip_1559_request = TransactionRequest { from: Address::ZERO, max_fee_per_gas: Some(U256::ZERO), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(spec, (&eip_1559_request).into()), + validate_transaction_spec::(spec, &eip_1559_request), Err(ProviderError::UnsupportedEIP1559Parameters { .. }) )); - let eip_1559_request = EthTransactionRequest { + let eip_1559_request = TransactionRequest { from: Address::ZERO, max_priority_fee_per_gas: Some(U256::ZERO), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(spec, (&eip_1559_request).into()), + validate_transaction_spec::(spec, &eip_1559_request), Err(ProviderError::UnsupportedEIP1559Parameters { .. }) )); } fn assert_unsupported_eip_4844_parameters(spec: SpecId) { - let eip_4844_request = EthTransactionRequest { + let eip_4844_request = TransactionRequest { from: Address::ZERO, blobs: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(spec, (&eip_4844_request).into()), + validate_transaction_spec::(spec, &eip_4844_request), Err(ProviderError::UnsupportedEIP4844Parameters { .. }) )); - let eip_4844_request = EthTransactionRequest { + let eip_4844_request = TransactionRequest { from: Address::ZERO, blob_hashes: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(spec, (&eip_4844_request).into()), + validate_transaction_spec::(spec, &eip_4844_request), Err(ProviderError::UnsupportedEIP4844Parameters { .. }) )); } @@ -390,22 +455,22 @@ mod tests { #[test] fn validate_transaction_spec_eip_155_invalid_inputs() { let eip155_spec = SpecId::MUIR_GLACIER; - let valid_request = EthTransactionRequest { + let valid_request = TransactionRequest { from: Address::ZERO, gas_price: Some(U256::ZERO), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; - assert!(validate_transaction_spec(eip155_spec, (&valid_request).into()).is_ok()); + assert!(validate_transaction_spec::(eip155_spec, &valid_request).is_ok()); - let eip_2930_request = EthTransactionRequest { + let eip_2930_request = TransactionRequest { from: Address::ZERO, access_list: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(eip155_spec, (&eip_2930_request).into()), + validate_transaction_spec::(eip155_spec, &eip_2930_request), Err(ProviderError::UnsupportedAccessListParameter { .. }) )); @@ -416,14 +481,14 @@ mod tests { #[test] fn validate_transaction_spec_eip_2930_invalid_inputs() { let eip2930_spec = SpecId::BERLIN; - let valid_request = EthTransactionRequest { + let valid_request = TransactionRequest { from: Address::ZERO, gas_price: Some(U256::ZERO), access_list: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; - assert!(validate_transaction_spec(eip2930_spec, (&valid_request).into()).is_ok()); + assert!(validate_transaction_spec::(eip2930_spec, &valid_request).is_ok()); assert_unsupported_eip_1559_parameters(eip2930_spec); assert_unsupported_eip_4844_parameters(eip2930_spec); @@ -432,15 +497,15 @@ mod tests { #[test] fn validate_transaction_spec_eip_1559_invalid_inputs() { let eip1559_spec = SpecId::LONDON; - let valid_request = EthTransactionRequest { + let valid_request = TransactionRequest { from: Address::ZERO, max_fee_per_gas: Some(U256::ZERO), max_priority_fee_per_gas: Some(U256::ZERO), access_list: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; - assert!(validate_transaction_spec(eip1559_spec, (&valid_request).into()).is_ok()); + assert!(validate_transaction_spec::(eip1559_spec, &valid_request).is_ok()); assert_unsupported_eip_4844_parameters(eip1559_spec); assert_mixed_eip_1559_parameters(eip1559_spec); @@ -449,7 +514,7 @@ mod tests { #[test] fn validate_transaction_spec_eip_4844_invalid_inputs() { let eip4844_spec = SpecId::CANCUN; - let valid_request = EthTransactionRequest { + let valid_request = TransactionRequest { from: Address::ZERO, to: Some(Address::ZERO), max_fee_per_gas: Some(U256::ZERO), @@ -457,55 +522,55 @@ mod tests { access_list: Some(Vec::new()), blobs: Some(Vec::new()), blob_hashes: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; - assert!(validate_transaction_spec(eip4844_spec, (&valid_request).into()).is_ok()); + assert!(validate_transaction_spec::(eip4844_spec, &valid_request).is_ok()); assert_mixed_eip_1559_parameters(eip4844_spec); - let mixed_request = EthTransactionRequest { + let mixed_request = TransactionRequest { from: Address::ZERO, gas_price: Some(U256::ZERO), blobs: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(eip4844_spec, (&mixed_request).into()), + validate_transaction_spec::(eip4844_spec, &mixed_request), Err(ProviderError::InvalidTransactionInput(_)) )); - let mixed_request = EthTransactionRequest { + let mixed_request = TransactionRequest { from: Address::ZERO, gas_price: Some(U256::ZERO), blob_hashes: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(eip4844_spec, (&mixed_request).into()), + validate_transaction_spec::(eip4844_spec, &mixed_request), Err(ProviderError::InvalidTransactionInput(_)) )); - let missing_receiver_request = EthTransactionRequest { + let missing_receiver_request = TransactionRequest { from: Address::ZERO, blobs: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(eip4844_spec, (&missing_receiver_request).into()), + validate_transaction_spec::(eip4844_spec, &missing_receiver_request), Err(ProviderError::Eip4844TransactionMissingReceiver) )); - let missing_receiver_request = EthTransactionRequest { + let missing_receiver_request = TransactionRequest { from: Address::ZERO, blob_hashes: Some(Vec::new()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; assert!(matches!( - validate_transaction_spec(eip4844_spec, (&missing_receiver_request).into()), + validate_transaction_spec::(eip4844_spec, &missing_receiver_request), Err(ProviderError::Eip4844TransactionMissingReceiver) )); } diff --git a/crates/edr_provider/src/spec.rs b/crates/edr_provider/src/spec.rs new file mode 100644 index 000000000..ce5d1f811 --- /dev/null +++ b/crates/edr_provider/src/spec.rs @@ -0,0 +1,158 @@ +use core::fmt::Debug; + +use edr_eth::{ + chain_spec::L1ChainSpec, + rlp, + transaction::{ + signed::{FakeSign, Sign}, + Transaction, + }, + AccessListItem, Address, Blob, BlockSpec, B256, U256, +}; +use edr_evm::{ + chain_spec::{ChainSpec, SyncChainSpec}, + state::StateOverrides, + transaction, +}; +use edr_rpc_eth::{CallRequest, TransactionRequest}; + +use crate::{data::ProviderData, time::TimeSinceEpoch, ProviderError, TransactionFailureReason}; + +pub trait ProviderSpec: + ChainSpec< + HaltReason: Into>, + Hardfork: Debug, + RpcCallRequest: MaybeSender + + for<'context> ResolveRpcType< + TimerT, + Self::TransactionRequest, + Context<'context> = CallContext<'context, Self, TimerT>, + Error = ProviderError, + >, + RpcTransactionRequest: Sender + + for<'context> ResolveRpcType< + TimerT, + Self::TransactionRequest, + Context<'context> = TransactionContext<'context, Self, TimerT>, + Error = ProviderError, + >, +> +{ + type PooledTransaction: HardforkValidationData + + Into + + rlp::Decodable + + Transaction; + + /// Type representing a transaction request. + type TransactionRequest: FakeSign + Sign; +} + +impl ProviderSpec for L1ChainSpec { + type PooledTransaction = transaction::pooled::PooledTransaction; + type TransactionRequest = transaction::Request; +} + +/// Trait with data used for validating a transaction complies with a +/// [`edr_eth::SpecId`]. +pub trait HardforkValidationData { + /// Returns the `to` address of the transaction. + fn to(&self) -> Option<&Address>; + + /// Returns the gas price of the transaction. + fn gas_price(&self) -> Option<&U256>; + + /// Returns the max fee per gas of the transaction. + fn max_fee_per_gas(&self) -> Option<&U256>; + + /// Returns the max priority fee per gas of the transaction. + fn max_priority_fee_per_gas(&self) -> Option<&U256>; + + /// Returns the access list of the transaction. + fn access_list(&self) -> Option<&Vec>; + + /// Returns the blobs of the transaction. + fn blobs(&self) -> Option<&Vec>; + + /// Returns the blob hashes of the transaction. + fn blob_hashes(&self) -> Option<&Vec>; +} + +/// Trait for retrieving the sender of a request, if any. +pub trait MaybeSender { + /// Retrieves the sender of the request, if any. + fn maybe_sender(&self) -> Option<&Address>; +} + +impl MaybeSender for CallRequest { + fn maybe_sender(&self) -> Option<&Address> { + self.from.as_ref() + } +} + +/// Trait for retrieving the sender of a request. +pub trait Sender { + /// Retrieves the sender of the request. + fn sender(&self) -> &Address; +} + +impl Sender for TransactionRequest { + fn sender(&self) -> &Address { + &self.from + } +} + +// ChainSpecT: ProviderSpec, + +/// Trait for resolving an RPC type to an internal type. +pub trait ResolveRpcType { + /// Type for contextual information. + type Context<'context>; + + /// Type of error that can occur during resolution. + type Error; + + fn resolve_rpc_type(self, context: Self::Context<'_>) -> Result; +} + +pub trait SyncProviderSpec: + ProviderSpec + SyncChainSpec +{ +} + +impl + SyncChainSpec, TimerT: Clone + TimeSinceEpoch> + SyncProviderSpec for ProviderSpecT +{ +} + +pub type DefaultGasPriceFn = + fn(&ProviderData) -> Result>; + +pub type MaxFeesFn = fn( + &ProviderData, + // block_spec + &BlockSpec, + // max_fee_per_gas + Option, + // max_priority_fee_per_gas + Option, +) -> Result<(U256, U256), ProviderError>; + +pub struct CallContext< + 'context, + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +> { + pub data: &'context mut ProviderData, + pub block_spec: &'context BlockSpec, + pub state_overrides: &'context StateOverrides, + pub default_gas_price_fn: DefaultGasPriceFn, + pub max_fees_fn: MaxFeesFn, +} + +pub struct TransactionContext< + 'context, + ChainSpecT: ProviderSpec, + TimerT: Clone + TimeSinceEpoch, +> { + pub data: &'context mut ProviderData, +} diff --git a/crates/edr_provider/src/subscribe.rs b/crates/edr_provider/src/subscribe.rs index 729b46e35..dc6a47a7d 100644 --- a/crates/edr_provider/src/subscribe.rs +++ b/crates/edr_provider/src/subscribe.rs @@ -1,25 +1,32 @@ +use derive_where::derive_where; use dyn_clone::DynClone; -use edr_eth::{chain_spec::L1ChainSpec, filter::LogOutput, B256, U256}; -use edr_evm::{blockchain::BlockchainError, BlockAndTotalDifficulty}; +use edr_eth::{filter::LogOutput, B256, U256}; +use edr_evm::{blockchain::BlockchainError, chain_spec::ChainSpec, BlockAndTotalDifficulty}; /// Subscription event. #[derive(Clone, Debug)] -pub struct SubscriptionEvent { +pub struct SubscriptionEvent { pub filter_id: U256, - pub result: SubscriptionEventData, + pub result: SubscriptionEventData, } /// Subscription event data. -#[derive(Clone, Debug)] -pub enum SubscriptionEventData { +#[derive_where(Clone, Debug)] +pub enum SubscriptionEventData { Logs(Vec), - NewHeads(BlockAndTotalDifficulty>), + NewHeads(BlockAndTotalDifficulty>), NewPendingTransactions(B256), } /// Supertrait for subscription callbacks. -pub trait SyncSubscriberCallback: Fn(SubscriptionEvent) + DynClone + Send + Sync {} +pub trait SyncSubscriberCallback: + Fn(SubscriptionEvent) + DynClone + Send + Sync +{ +} -impl SyncSubscriberCallback for F where F: Fn(SubscriptionEvent) + DynClone + Send + Sync {} +impl SyncSubscriberCallback for F where + F: Fn(SubscriptionEvent) + DynClone + Send + Sync +{ +} -dyn_clone::clone_trait_object!(SyncSubscriberCallback); +dyn_clone::clone_trait_object!( SyncSubscriberCallback where ChainSpecT: ChainSpec); diff --git a/crates/edr_provider/src/test_utils.rs b/crates/edr_provider/src/test_utils.rs index c40483ce3..c9d5c396a 100644 --- a/crates/edr_provider/src/test_utils.rs +++ b/crates/edr_provider/src/test_utils.rs @@ -1,10 +1,11 @@ use std::{num::NonZeroU64, time::SystemTime}; use edr_eth::{ - block::BlobGas, signature::secret_key_from_str, transaction::EthTransactionRequest, - trie::KECCAK_NULL_RLP, Address, Bytes, HashMap, SpecId, B256, U160, U256, + block::BlobGas, chain_spec::L1ChainSpec, signature::secret_key_from_str, trie::KECCAK_NULL_RLP, + Address, Bytes, HashMap, B256, U160, U256, }; use edr_evm::Block; +use edr_rpc_eth::TransactionRequest; use super::*; use crate::{config::MiningConfig, requests::hardhat::rpc_types::ForkConfig}; @@ -19,7 +20,7 @@ pub const TEST_SECRET_KEY_SIGN_TYPED_DATA_V4: &str = pub const FORK_BLOCK_NUMBER: u64 = 18_725_000; /// Constructs a test config with a single account with 1 ether -pub fn create_test_config() -> ProviderConfig { +pub fn create_test_config() -> ProviderConfig { create_test_config_with_fork(None) } @@ -27,7 +28,9 @@ pub fn one_ether() -> U256 { U256::from(10).pow(U256::from(18)) } -pub fn create_test_config_with_fork(fork: Option) -> ProviderConfig { +pub fn create_test_config_with_fork( + fork: Option, +) -> ProviderConfig { ProviderConfig { accounts: vec![ AccountConfig { @@ -53,7 +56,7 @@ pub fn create_test_config_with_fork(fork: Option) -> ProviderConfig< enable_rip_7212: false, fork, genesis_accounts: HashMap::new(), - hardfork: SpecId::LATEST, + hardfork: ChainSpecT::Hardfork::default(), initial_base_fee_per_gas: Some(U256::from(1000000000)), initial_blob_gas: Some(BlobGas { gas_used: 0, @@ -69,7 +72,19 @@ pub fn create_test_config_with_fork(fork: Option) -> ProviderConfig< } /// Retrieves the pending base fee per gas from the provider data. -pub fn pending_base_fee(data: &mut ProviderData) -> Result { +pub fn pending_base_fee< + ChainSpecT: SyncProviderSpec< + TimerT, + Block: Default, + Transaction: Default + + TransactionValidation< + ValidationError: From + PartialEq, + >, + >, + TimerT: Clone + TimeSinceEpoch, +>( + data: &mut ProviderData, +) -> Result> { let block = data.mine_pending_block()?.block; let base_fee = block @@ -83,17 +98,17 @@ pub fn pending_base_fee(data: &mut ProviderData) -> Result /// Deploys a contract with the provided code. Returns the address of the /// contract. pub fn deploy_contract( - provider: &Provider, + provider: &Provider, caller: Address, code: Bytes, ) -> anyhow::Result
where TimerT: Clone + TimeSinceEpoch, { - let deploy_transaction = EthTransactionRequest { + let deploy_transaction = TransactionRequest { from: caller, data: Some(code), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }; let result = provider.handle_request(ProviderRequest::Single( diff --git a/crates/edr_provider/tests/eip4844.rs b/crates/edr_provider/tests/eip4844.rs index 6763d1ee0..79b7b467f 100644 --- a/crates/edr_provider/tests/eip4844.rs +++ b/crates/edr_provider/tests/eip4844.rs @@ -8,8 +8,8 @@ use edr_eth::{ rlp::{self, Decodable}, signature::{secret_key_from_str, secret_key_to_address}, transaction::{ - self, pooled::PooledTransaction, EthTransactionRequest, SignedTransaction as _, - Transaction as _, TransactionType as _, + self, pooled::PooledTransaction, ExecutableTransaction as _, Transaction as _, + TransactionType as _, }, AccountInfo, Address, Blob, Bytes, Bytes48, PreEip1898BlockSpec, SpecId, B256, BYTES_PER_BLOB, KECCAK_EMPTY, U256, @@ -20,7 +20,7 @@ use edr_provider::{ time::CurrentTime, MethodInvocation, NoopLogger, Provider, ProviderError, ProviderRequest, }; -use edr_rpc_eth::CallRequest; +use edr_rpc_eth::{CallRequest, TransactionRequest}; use tokio::runtime; /// Helper struct to modify the pooled transaction from the value in @@ -140,14 +140,9 @@ fn fake_transaction() -> transaction::Signed { fake_pooled_transaction().into_payload() } -fn fake_call_request() -> anyhow::Result { +fn fake_call_request() -> CallRequest { let transaction = fake_pooled_transaction(); - let blobs = transaction.blobs().map(|blobs| { - blobs - .iter() - .map(|blob| Bytes::copy_from_slice(blob.as_ref())) - .collect() - }); + let blobs = transaction.blobs().map(<[Blob]>::to_vec); let transaction = transaction.into_payload(); let from = transaction.caller(); @@ -163,10 +158,10 @@ fn fake_call_request() -> anyhow::Result { None }; - Ok(CallRequest { + CallRequest { from: Some(*from), to: transaction.kind().to().copied(), - max_fee_per_gas: transaction.max_fee_per_gas(), + max_fee_per_gas: transaction.max_fee_per_gas().copied(), max_priority_fee_per_gas: transaction.max_priority_fee_per_gas().cloned(), gas: Some(transaction.gas_limit()), value: Some(*transaction.value()), @@ -175,17 +170,12 @@ fn fake_call_request() -> anyhow::Result { blobs, blob_hashes, ..CallRequest::default() - }) + } } -fn fake_transaction_request() -> EthTransactionRequest { +fn fake_transaction_request() -> TransactionRequest { let transaction = fake_pooled_transaction(); - let blobs = transaction.blobs().map(|blobs| { - blobs - .iter() - .map(|blob| Bytes::copy_from_slice(blob.as_ref())) - .collect() - }); + let blobs = transaction.blobs().map(<[edr_eth::Blob]>::to_vec); let transaction = transaction.into_payload(); let from = *transaction.caller(); @@ -202,10 +192,10 @@ fn fake_transaction_request() -> EthTransactionRequest { None }; - EthTransactionRequest { + TransactionRequest { from, to: transaction.kind().to().copied(), - max_fee_per_gas: transaction.max_fee_per_gas(), + max_fee_per_gas: transaction.max_fee_per_gas().copied(), max_priority_fee_per_gas: transaction.max_priority_fee_per_gas().cloned(), gas: Some(transaction.gas_limit()), value: Some(*transaction.value()), @@ -216,13 +206,13 @@ fn fake_transaction_request() -> EthTransactionRequest { transaction_type: Some(transaction.transaction_type().into()), blobs, blob_hashes, - ..EthTransactionRequest::default() + ..TransactionRequest::default() } } #[tokio::test(flavor = "multi_thread")] async fn call_unsupported() -> anyhow::Result<()> { - let request = fake_call_request()?; + let request = fake_call_request(); let logger = Box::new(NoopLogger::::default()); let subscriber = Box::new(|_event| {}); @@ -253,7 +243,7 @@ async fn call_unsupported() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn estimate_gas_unsupported() -> anyhow::Result<()> { - let request = fake_call_request()?; + let request = fake_call_request(); let logger = Box::new(NoopLogger::::default()); let subscriber = Box::new(|_event| {}); @@ -391,7 +381,7 @@ async fn get_transaction() -> anyhow::Result<()> { MethodInvocation::GetTransactionByHash(transaction_hash), ))?; - let transaction: edr_rpc_eth::Transaction = serde_json::from_value(result.result)?; + let transaction: edr_rpc_eth::TransactionWithSignature = serde_json::from_value(result.result)?; let transaction = transaction::Signed::try_from(transaction)?; assert_eq!(transaction, expected); @@ -538,7 +528,7 @@ async fn block_header() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn blob_hash_opcode() -> anyhow::Result<()> { fn assert_blob_hash_opcodes( - provider: &Provider, + provider: &Provider, contract_address: &Address, num_blobs: usize, nonce: u64, diff --git a/crates/edr_provider/tests/eth_request_serialization.rs b/crates/edr_provider/tests/eth_request_serialization.rs index 4cddcae8a..6ef43c66d 100644 --- a/crates/edr_provider/tests/eth_request_serialization.rs +++ b/crates/edr_provider/tests/eth_request_serialization.rs @@ -1,12 +1,13 @@ mod common; use edr_eth::{ + chain_spec::L1ChainSpec, filter::{LogFilterOptions, LogOutput, OneOrMore}, - transaction::EthTransactionRequest, - Address, BlockSpec, BlockTag, Bytes, PreEip1898BlockSpec, B256, U160, U256, + Address, Blob, BlockSpec, BlockTag, Bytes, PreEip1898BlockSpec, B256, BYTES_PER_BLOB, U160, + U256, }; use edr_provider::{IntervalConfigRequest, MethodInvocation, Timestamp}; -use edr_rpc_eth::CallRequest; +use edr_rpc_eth::{CallRequest, TransactionRequest}; use crate::common::{ help_test_method_invocation_serde, help_test_method_invocation_serde_with_expected, @@ -14,12 +15,12 @@ use crate::common::{ #[test] fn test_serde_eth_accounts() { - help_test_method_invocation_serde(MethodInvocation::Accounts(())); + help_test_method_invocation_serde(MethodInvocation::::Accounts(())); } #[test] fn test_serde_eth_block_number() { - help_test_method_invocation_serde(MethodInvocation::BlockNumber(())); + help_test_method_invocation_serde(MethodInvocation::::BlockNumber(())); } #[test] @@ -35,28 +36,28 @@ fn test_serde_eth_call() { data: Some(Bytes::from(&b"whatever"[..])), access_list: None, transaction_type: None, - blobs: Some(vec![Bytes::from("0x1234")]), + blobs: Some(vec![Blob::new([1u8; BYTES_PER_BLOB])]), blob_hashes: Some(vec![B256::from(U256::from(1))]), }; - help_test_method_invocation_serde(MethodInvocation::Call( + help_test_method_invocation_serde(MethodInvocation::::Call( tx.clone(), Some(BlockSpec::latest()), None, )); help_test_method_invocation_serde_with_expected( - MethodInvocation::Call(tx.clone(), None, None), - MethodInvocation::Call(tx, Some(BlockSpec::latest()), None), + MethodInvocation::::Call(tx.clone(), None, None), + MethodInvocation::::Call(tx, Some(BlockSpec::latest()), None), ); } #[test] fn test_serde_eth_chain_id() { - help_test_method_invocation_serde(MethodInvocation::ChainId(())); + help_test_method_invocation_serde(MethodInvocation::::ChainId(())); } #[test] fn test_serde_eth_coinbase() { - help_test_method_invocation_serde(MethodInvocation::Coinbase(())); + help_test_method_invocation_serde(MethodInvocation::::Coinbase(())); } #[test] @@ -75,19 +76,19 @@ fn test_serde_eth_estimate_gas() { blobs: None, blob_hashes: None, }; - help_test_method_invocation_serde(MethodInvocation::EstimateGas( + help_test_method_invocation_serde(MethodInvocation::::EstimateGas( tx.clone(), Some(BlockSpec::latest()), )); help_test_method_invocation_serde_with_expected( - MethodInvocation::EstimateGas(tx.clone(), None), - MethodInvocation::EstimateGas(tx, Some(BlockSpec::pending())), + MethodInvocation::::EstimateGas(tx.clone(), None), + MethodInvocation::::EstimateGas(tx, Some(BlockSpec::pending())), ); } #[test] fn test_serde_eth_fee_history() { - help_test_method_invocation_serde(MethodInvocation::FeeHistory( + help_test_method_invocation_serde(MethodInvocation::::FeeHistory( U256::from(3), BlockSpec::Number(100), Some(vec![0.5_f64, 10_f64, 80_f64, 90_f64, 99.5_f64]), @@ -96,24 +97,27 @@ fn test_serde_eth_fee_history() { #[test] fn test_serde_eth_gas_price() { - help_test_method_invocation_serde(MethodInvocation::GasPrice(())); + help_test_method_invocation_serde(MethodInvocation::::GasPrice(())); } #[test] fn test_serde_eth_get_balance() { - help_test_method_invocation_serde(MethodInvocation::GetBalance( + help_test_method_invocation_serde(MethodInvocation::::GetBalance( Address::from(U160::from(1)), Some(BlockSpec::latest()), )); help_test_method_invocation_serde_with_expected( - MethodInvocation::GetBalance(Address::from(U160::from(1)), None), - MethodInvocation::GetBalance(Address::from(U160::from(1)), Some(BlockSpec::latest())), + MethodInvocation::::GetBalance(Address::from(U160::from(1)), None), + MethodInvocation::::GetBalance( + Address::from(U160::from(1)), + Some(BlockSpec::latest()), + ), ); } #[test] fn test_serde_eth_get_block_by_number() { - help_test_method_invocation_serde(MethodInvocation::GetBlockByNumber( + help_test_method_invocation_serde(MethodInvocation::::GetBlockByNumber( PreEip1898BlockSpec::Number(100), true, )); @@ -121,7 +125,7 @@ fn test_serde_eth_get_block_by_number() { #[test] fn test_serde_eth_get_block_by_tag() { - help_test_method_invocation_serde(MethodInvocation::GetBlockByNumber( + help_test_method_invocation_serde(MethodInvocation::::GetBlockByNumber( PreEip1898BlockSpec::latest(), true, )); @@ -129,7 +133,7 @@ fn test_serde_eth_get_block_by_tag() { #[test] fn test_serde_eth_get_block_by_hash() { - help_test_method_invocation_serde(MethodInvocation::GetBlockByHash( + help_test_method_invocation_serde(MethodInvocation::::GetBlockByHash( B256::from(U256::from(1)), true, )); @@ -137,13 +141,13 @@ fn test_serde_eth_get_block_by_hash() { #[test] fn test_serde_eth_get_transaction_count() { - help_test_method_invocation_serde(MethodInvocation::GetTransactionCount( + help_test_method_invocation_serde(MethodInvocation::::GetTransactionCount( Address::from(U160::from(1)), Some(BlockSpec::latest()), )); help_test_method_invocation_serde_with_expected( - MethodInvocation::GetTransactionCount(Address::from(U160::from(1)), None), - MethodInvocation::GetTransactionCount( + MethodInvocation::::GetTransactionCount(Address::from(U160::from(1)), None), + MethodInvocation::::GetTransactionCount( Address::from(U160::from(1)), Some(BlockSpec::latest()), ), @@ -152,43 +156,52 @@ fn test_serde_eth_get_transaction_count() { #[test] fn test_serde_eth_get_transaction() { - help_test_method_invocation_serde(MethodInvocation::GetBlockTransactionCountByHash( - B256::from(U256::from(1)), - )); + help_test_method_invocation_serde( + MethodInvocation::::GetBlockTransactionCountByHash(B256::from(U256::from(1))), + ); } #[test] fn test_serde_eth_get_transaction_count_by_number() { - help_test_method_invocation_serde(MethodInvocation::GetBlockTransactionCountByNumber( - PreEip1898BlockSpec::Number(100), - )); + help_test_method_invocation_serde( + MethodInvocation::::GetBlockTransactionCountByNumber( + PreEip1898BlockSpec::Number(100), + ), + ); } #[test] fn test_serde_eth_get_code() { - help_test_method_invocation_serde(MethodInvocation::GetCode( + help_test_method_invocation_serde(MethodInvocation::::GetCode( Address::from(U160::from(1)), Some(BlockSpec::latest()), )); help_test_method_invocation_serde_with_expected( - MethodInvocation::GetCode(Address::from(U160::from(1)), None), - MethodInvocation::GetCode(Address::from(U160::from(1)), Some(BlockSpec::latest())), + MethodInvocation::::GetCode(Address::from(U160::from(1)), None), + MethodInvocation::::GetCode( + Address::from(U160::from(1)), + Some(BlockSpec::latest()), + ), ); } #[test] fn test_serde_eth_get_filter_changes() { - help_test_method_invocation_serde(MethodInvocation::GetFilterChanges(U256::from(100))); + help_test_method_invocation_serde(MethodInvocation::::GetFilterChanges( + U256::from(100), + )); } #[test] fn test_serde_eth_get_filter_logs() { - help_test_method_invocation_serde(MethodInvocation::GetFilterLogs(U256::from(100))); + help_test_method_invocation_serde(MethodInvocation::::GetFilterLogs(U256::from( + 100, + ))); } #[test] fn test_serde_eth_get_logs_by_block_numbers() { - help_test_method_invocation_serde(MethodInvocation::GetLogs(LogFilterOptions { + help_test_method_invocation_serde(MethodInvocation::::GetLogs(LogFilterOptions { from_block: Some(BlockSpec::Number(100)), to_block: Some(BlockSpec::Number(102)), block_hash: None, @@ -199,7 +212,7 @@ fn test_serde_eth_get_logs_by_block_numbers() { #[test] fn test_serde_eth_get_logs_by_block_tags() { - help_test_method_invocation_serde(MethodInvocation::GetLogs(LogFilterOptions { + help_test_method_invocation_serde(MethodInvocation::::GetLogs(LogFilterOptions { from_block: Some(BlockSpec::Tag(BlockTag::Safe)), to_block: Some(BlockSpec::latest()), block_hash: None, @@ -210,7 +223,7 @@ fn test_serde_eth_get_logs_by_block_tags() { #[test] fn test_serde_eth_get_logs_by_block_hash() { - help_test_method_invocation_serde(MethodInvocation::GetLogs(LogFilterOptions { + help_test_method_invocation_serde(MethodInvocation::::GetLogs(LogFilterOptions { from_block: None, to_block: None, block_hash: Some(B256::from(U256::from(1))), @@ -221,14 +234,18 @@ fn test_serde_eth_get_logs_by_block_hash() { #[test] fn test_serde_eth_get_storage_at() { - help_test_method_invocation_serde(MethodInvocation::GetStorageAt( + help_test_method_invocation_serde(MethodInvocation::::GetStorageAt( Address::from(U160::from(1)), U256::ZERO, Some(BlockSpec::latest()), )); help_test_method_invocation_serde_with_expected( - MethodInvocation::GetStorageAt(Address::from(U160::from(1)), U256::ZERO, None), - MethodInvocation::GetStorageAt( + MethodInvocation::::GetStorageAt( + Address::from(U160::from(1)), + U256::ZERO, + None, + ), + MethodInvocation::::GetStorageAt( Address::from(U160::from(1)), U256::ZERO, Some(BlockSpec::latest()), @@ -238,30 +255,34 @@ fn test_serde_eth_get_storage_at() { #[test] fn test_serde_eth_get_tx_by_block_hash_and_index() { - help_test_method_invocation_serde(MethodInvocation::GetTransactionByBlockHashAndIndex( - B256::from(U256::from(1)), - U256::from(1), - )); + help_test_method_invocation_serde( + MethodInvocation::::GetTransactionByBlockHashAndIndex( + B256::from(U256::from(1)), + U256::from(1), + ), + ); } #[test] fn test_serde_eth_get_tx_by_block_number_and_index() { - help_test_method_invocation_serde(MethodInvocation::GetTransactionByBlockNumberAndIndex( - PreEip1898BlockSpec::Number(100), - U256::from(1), - )); + help_test_method_invocation_serde( + MethodInvocation::::GetTransactionByBlockNumberAndIndex( + PreEip1898BlockSpec::Number(100), + U256::from(1), + ), + ); } #[test] fn test_serde_eth_get_tx_by_hash() { - help_test_method_invocation_serde(MethodInvocation::GetTransactionByHash(B256::from( - U256::from(1), - ))); + help_test_method_invocation_serde(MethodInvocation::::GetTransactionByHash( + B256::from(U256::from(1)), + )); } #[test] fn test_serde_eth_get_tx_count_by_block_number() { - help_test_method_invocation_serde(MethodInvocation::GetTransactionCount( + help_test_method_invocation_serde(MethodInvocation::::GetTransactionCount( Address::from(U160::from(1)), Some(BlockSpec::Number(100)), )); @@ -269,7 +290,7 @@ fn test_serde_eth_get_tx_count_by_block_number() { #[test] fn test_serde_eth_get_tx_count_by_block_tag() { - help_test_method_invocation_serde(MethodInvocation::GetTransactionCount( + help_test_method_invocation_serde(MethodInvocation::::GetTransactionCount( Address::from(U160::from(1)), Some(BlockSpec::latest()), )); @@ -277,72 +298,78 @@ fn test_serde_eth_get_tx_count_by_block_tag() { #[test] fn test_serde_eth_get_tx_receipt() { - help_test_method_invocation_serde(MethodInvocation::GetTransactionReceipt(B256::from( - U256::from(1), - ))); + help_test_method_invocation_serde(MethodInvocation::::GetTransactionReceipt( + B256::from(U256::from(1)), + )); } #[test] fn test_serde_eth_mining() { - help_test_method_invocation_serde(MethodInvocation::Mining(())); + help_test_method_invocation_serde(MethodInvocation::::Mining(())); } #[test] fn test_serde_eth_new_block_filter() { - help_test_method_invocation_serde(MethodInvocation::NewBlockFilter(())); + help_test_method_invocation_serde(MethodInvocation::::NewBlockFilter(())); } #[test] fn test_serde_eth_new_filter() { - help_test_method_invocation_serde(MethodInvocation::NewFilter(LogFilterOptions { - from_block: Some(BlockSpec::Number(1000)), - to_block: Some(BlockSpec::latest()), - block_hash: None, - address: Some(OneOrMore::One(Address::from(U160::from(1)))), - topics: Some(vec![Some(OneOrMore::One(B256::from(U256::from(1))))]), - })); + help_test_method_invocation_serde(MethodInvocation::::NewFilter( + LogFilterOptions { + from_block: Some(BlockSpec::Number(1000)), + to_block: Some(BlockSpec::latest()), + block_hash: None, + address: Some(OneOrMore::One(Address::from(U160::from(1)))), + topics: Some(vec![Some(OneOrMore::One(B256::from(U256::from(1))))]), + }, + )); } #[test] fn test_serde_eth_new_pending_transaction_filter() { - help_test_method_invocation_serde(MethodInvocation::NewPendingTransactionFilter(())); + help_test_method_invocation_serde( + MethodInvocation::::NewPendingTransactionFilter(()), + ); } #[test] fn test_serde_eth_pending_transactions() { - help_test_method_invocation_serde(MethodInvocation::PendingTransactions(())); + help_test_method_invocation_serde(MethodInvocation::::PendingTransactions(())); } #[test] fn test_serde_eth_send_raw_transaction() { - help_test_method_invocation_serde(MethodInvocation::SendRawTransaction(Bytes::from( - &b"whatever"[..], - ))); + help_test_method_invocation_serde(MethodInvocation::::SendRawTransaction( + Bytes::from(&b"whatever"[..]), + )); } #[test] fn test_serde_eth_send_transaction() { - help_test_method_invocation_serde(MethodInvocation::SendTransaction(EthTransactionRequest { - from: Address::from(U160::from(1)), - to: Some(Address::from(U160::from(2))), - gas: Some(3_u64), - gas_price: Some(U256::from(4)), - max_fee_per_gas: None, - value: Some(U256::from(123568919)), - data: Some(Bytes::from(&b"whatever"[..])), - nonce: None, - chain_id: None, - access_list: None, - max_priority_fee_per_gas: None, - transaction_type: None, - blobs: Some(vec![Bytes::from("0x1234")]), - blob_hashes: Some(vec![B256::from(U256::from(1))]), - })); + help_test_method_invocation_serde(MethodInvocation::::SendTransaction( + TransactionRequest { + from: Address::from(U160::from(1)), + to: Some(Address::from(U160::from(2))), + gas: Some(3_u64), + gas_price: Some(U256::from(4)), + max_fee_per_gas: None, + value: Some(U256::from(123568919)), + data: Some(Bytes::from(&b"whatever"[..])), + nonce: None, + chain_id: None, + access_list: None, + max_priority_fee_per_gas: None, + transaction_type: None, + blobs: Some(vec![Blob::new([1u8; BYTES_PER_BLOB])]), + blob_hashes: Some(vec![B256::from(U256::from(1))]), + }, + )); } #[test] fn test_serde_personal_sign() { - help_test_method_invocation_serde(MethodInvocation::PersonalSign( + help_test_method_invocation_serde(MethodInvocation::::PersonalSign( Bytes::from(&b"whatever"[..]), Address::from(U160::from(1)), )); @@ -350,7 +377,7 @@ fn test_serde_personal_sign() { #[test] fn test_serde_eth_sign() { - help_test_method_invocation_serde(MethodInvocation::EthSign( + help_test_method_invocation_serde(MethodInvocation::::EthSign( Address::from(U160::from(1)), Bytes::from(&b"whatever"[..]), )); @@ -366,14 +393,14 @@ macro_rules! impl_serde_eth_subscribe_tests { fn []() { use edr_eth::filter::SubscriptionType; - help_test_method_invocation_serde(MethodInvocation::Subscribe($variant, None)); + help_test_method_invocation_serde(MethodInvocation::::Subscribe($variant, None)); } #[test] fn []() { use edr_eth::filter::SubscriptionType; - help_test_method_invocation_serde(MethodInvocation::Subscribe($variant, Some(LogFilterOptions { + help_test_method_invocation_serde(MethodInvocation::::Subscribe($variant, Some(LogFilterOptions { from_block: Some(BlockSpec::Number(1000)), to_block: Some(BlockSpec::latest()), block_hash: None, @@ -394,17 +421,21 @@ impl_serde_eth_subscribe_tests! { #[test] fn test_serde_eth_syncing() { - help_test_method_invocation_serde(MethodInvocation::Syncing(())); + help_test_method_invocation_serde(MethodInvocation::::Syncing(())); } #[test] fn test_serde_eth_uninstall_filter() { - help_test_method_invocation_serde(MethodInvocation::UninstallFilter(U256::from(100))); + help_test_method_invocation_serde(MethodInvocation::::UninstallFilter( + U256::from(100), + )); } #[test] fn test_serde_eth_unsubscribe() { - help_test_method_invocation_serde(MethodInvocation::Unsubscribe(U256::from(100))); + help_test_method_invocation_serde(MethodInvocation::::Unsubscribe(U256::from( + 100, + ))); } fn help_test_serde_value(value: T) @@ -445,70 +476,78 @@ fn test_serde_one_or_more_addresses() { #[test] fn test_evm_increase_time() { - help_test_method_invocation_serde(MethodInvocation::EvmIncreaseTime(Timestamp::from(12345))); + help_test_method_invocation_serde(MethodInvocation::::EvmIncreaseTime( + Timestamp::from(12345), + )); } #[test] fn test_evm_mine() { - help_test_method_invocation_serde(MethodInvocation::EvmMine(Some(Timestamp::from(12345)))); - help_test_method_invocation_serde(MethodInvocation::EvmMine(None)); + help_test_method_invocation_serde(MethodInvocation::::EvmMine(Some( + Timestamp::from(12345), + ))); + help_test_method_invocation_serde(MethodInvocation::::EvmMine(None)); } #[test] fn test_evm_set_next_block_timestamp() { - help_test_method_invocation_serde(MethodInvocation::EvmSetNextBlockTimestamp(Timestamp::from( - 12345, - ))); + help_test_method_invocation_serde(MethodInvocation::::EvmSetNextBlockTimestamp( + Timestamp::from(12345), + )); } #[test] fn test_serde_web3_client_version() { - help_test_method_invocation_serde(MethodInvocation::Web3ClientVersion(())); + help_test_method_invocation_serde(MethodInvocation::::Web3ClientVersion(())); } #[test] fn test_serde_web3_sha3() { - help_test_method_invocation_serde(MethodInvocation::Web3Sha3(Bytes::from(&b"whatever"[..]))); + help_test_method_invocation_serde(MethodInvocation::::Web3Sha3(Bytes::from( + &b"whatever"[..], + ))); } #[test] fn test_evm_set_automine() { - help_test_method_invocation_serde(MethodInvocation::EvmSetAutomine(false)); + help_test_method_invocation_serde(MethodInvocation::::EvmSetAutomine(false)); } #[test] fn test_evm_set_interval_mining() { - help_test_method_invocation_serde(MethodInvocation::EvmSetIntervalMining( + help_test_method_invocation_serde(MethodInvocation::::EvmSetIntervalMining( IntervalConfigRequest::FixedOrDisabled(1000), )); - help_test_method_invocation_serde(MethodInvocation::EvmSetIntervalMining( + help_test_method_invocation_serde(MethodInvocation::::EvmSetIntervalMining( IntervalConfigRequest::Range([1000, 5000]), )); } #[test] fn test_evm_snapshot() { - help_test_method_invocation_serde(MethodInvocation::EvmSnapshot(())); + help_test_method_invocation_serde(MethodInvocation::::EvmSnapshot(())); } #[test] fn test_net_listening() { - help_test_method_invocation_serde(MethodInvocation::NetListening(())); + help_test_method_invocation_serde(MethodInvocation::::NetListening(())); } #[test] fn test_net_peer_count() { - help_test_method_invocation_serde(MethodInvocation::NetPeerCount(())); + help_test_method_invocation_serde(MethodInvocation::::NetPeerCount(())); } #[test] fn test_personal_sign() { - let call = - MethodInvocation::PersonalSign(Bytes::from(&b"whatever"[..]), Address::from(U160::from(1))); + let call = MethodInvocation::::PersonalSign( + Bytes::from(&b"whatever"[..]), + Address::from(U160::from(1)), + ); let serialized = serde_json::json!(call).to_string(); - let call_deserialized: MethodInvocation = serde_json::from_str(&serialized) + let call_deserialized: MethodInvocation = serde_json::from_str(&serialized) .unwrap_or_else(|_| panic!("should have successfully deserialized json {serialized}")); assert_eq!(call, call_deserialized); @@ -516,12 +555,14 @@ fn test_personal_sign() { #[test] fn test_eth_sign() { - let call = - MethodInvocation::EthSign(Address::from(U160::from(1)), Bytes::from(&b"whatever"[..])); + let call = MethodInvocation::::EthSign( + Address::from(U160::from(1)), + Bytes::from(&b"whatever"[..]), + ); let serialized = serde_json::json!(call).to_string(); - let call_deserialized: MethodInvocation = serde_json::from_str(&serialized) + let call_deserialized: MethodInvocation = serde_json::from_str(&serialized) .unwrap_or_else(|_| panic!("should have successfully deserialized json {serialized}")); assert_eq!(call, call_deserialized); diff --git a/crates/edr_provider/tests/hardhat_request_serialization.rs b/crates/edr_provider/tests/hardhat_request_serialization.rs index bd9c49ade..844db204a 100644 --- a/crates/edr_provider/tests/hardhat_request_serialization.rs +++ b/crates/edr_provider/tests/hardhat_request_serialization.rs @@ -1,6 +1,6 @@ mod common; -use edr_eth::{Address, Bytes, B256, U160, U256}; +use edr_eth::{chain_spec::L1ChainSpec, Address, Bytes, B256, U160, U256}; use edr_provider::{ hardhat_rpc_types::{CompilerInput, CompilerOutput, ForkConfig, ResetProviderConfig}, MethodInvocation, @@ -14,7 +14,7 @@ fn serde_hardhat_compiler() { let compiler_input_json = include_str!("fixtures/compiler_input.json"); let compiler_output_json = include_str!("fixtures/compiler_output.json"); - let call = MethodInvocation::AddCompilationResult( + let call = MethodInvocation::::AddCompilationResult( String::from("0.8.0"), serde_json::from_str::(compiler_input_json).unwrap(), serde_json::from_str::(compiler_output_json).unwrap(), @@ -45,63 +45,69 @@ fn serde_hardhat_compiler() { #[test] fn serde_hardhat_drop_transaction() { - help_test_method_invocation_serde(MethodInvocation::DropTransaction(B256::from(U256::from(1)))); + help_test_method_invocation_serde(MethodInvocation::::DropTransaction( + B256::from(U256::from(1)), + )); } #[test] fn serde_hardhat_get_automine() { - help_test_method_invocation_serde(MethodInvocation::GetAutomine(())); + help_test_method_invocation_serde(MethodInvocation::::GetAutomine(())); } #[test] fn serde_hardhat_get_stack_trace_failures_count() { - help_test_method_invocation_serde(MethodInvocation::GetStackTraceFailuresCount(())); + help_test_method_invocation_serde(MethodInvocation::::GetStackTraceFailuresCount( + (), + )); } #[test] fn serde_hardhat_impersonate_account() { - help_test_method_invocation_serde(MethodInvocation::ImpersonateAccount( + help_test_method_invocation_serde(MethodInvocation::::ImpersonateAccount( Address::from(U160::from(1)).into(), )); } #[test] fn serde_hardhat_interval_mine() { - help_test_method_invocation_serde(MethodInvocation::IntervalMine(())); + help_test_method_invocation_serde(MethodInvocation::::IntervalMine(())); } #[test] fn serde_hardhat_metadata() { - help_test_method_invocation_serde(MethodInvocation::Metadata(())); + help_test_method_invocation_serde(MethodInvocation::::Metadata(())); } #[test] fn serde_hardhat_mine() { - help_test_method_invocation_serde(MethodInvocation::Mine(Some(1), Some(1))); - help_test_method_invocation_serde(MethodInvocation::Mine(Some(1), None)); - help_test_method_invocation_serde(MethodInvocation::Mine(None, Some(1))); - help_test_method_invocation_serde(MethodInvocation::Mine(None, None)); + help_test_method_invocation_serde(MethodInvocation::::Mine(Some(1), Some(1))); + help_test_method_invocation_serde(MethodInvocation::::Mine(Some(1), None)); + help_test_method_invocation_serde(MethodInvocation::::Mine(None, Some(1))); + help_test_method_invocation_serde(MethodInvocation::::Mine(None, None)); let json = r#"{"jsonrpc":"2.0","method":"hardhat_mine","params":[],"id":2}"#; - let deserialized: MethodInvocation = serde_json::from_str(json) + let deserialized: MethodInvocation = serde_json::from_str(json) .unwrap_or_else(|_| panic!("should have successfully deserialized json {json}")); assert_eq!(MethodInvocation::Mine(None, None), deserialized); } #[test] fn serde_hardhat_reset() { - help_test_method_invocation_serde(MethodInvocation::Reset(Some(ResetProviderConfig { - forking: Some(ForkConfig { - json_rpc_url: String::from("http://whatever.com/whatever"), - block_number: Some(123456), - http_headers: None, - }), - }))); + help_test_method_invocation_serde(MethodInvocation::::Reset(Some( + ResetProviderConfig { + forking: Some(ForkConfig { + json_rpc_url: String::from("http://whatever.com/whatever"), + block_number: Some(123456), + http_headers: None, + }), + }, + ))); } #[test] fn serde_hardhat_set_balance() { - help_test_method_invocation_serde(MethodInvocation::SetBalance( + help_test_method_invocation_serde(MethodInvocation::::SetBalance( Address::from(U160::from(1)), U256::ZERO, )); @@ -109,7 +115,7 @@ fn serde_hardhat_set_balance() { #[test] fn serde_hardhat_set_code() { - help_test_method_invocation_serde(MethodInvocation::SetCode( + help_test_method_invocation_serde(MethodInvocation::::SetCode( Address::from(U160::from(1)), Bytes::from(&b"whatever"[..]), )); @@ -117,37 +123,48 @@ fn serde_hardhat_set_code() { #[test] fn serde_hardhat_set_coinbase() { - help_test_method_invocation_serde(MethodInvocation::SetCoinbase(Address::random())); + help_test_method_invocation_serde(MethodInvocation::::SetCoinbase( + Address::random(), + )); } #[test] fn serde_hardhat_set_logging_enabled() { - help_test_method_invocation_serde(MethodInvocation::SetLoggingEnabled(true)); + help_test_method_invocation_serde(MethodInvocation::::SetLoggingEnabled(true)); } #[test] fn serde_hardhat_set_min_gas_price() { - help_test_method_invocation_serde(MethodInvocation::SetMinGasPrice(U256::from(1))); + help_test_method_invocation_serde(MethodInvocation::::SetMinGasPrice(U256::from( + 1, + ))); } #[test] fn serde_hardhat_set_next_block_base_fee_per_gas() { - help_test_method_invocation_serde(MethodInvocation::SetNextBlockBaseFeePerGas(U256::from(1))); + help_test_method_invocation_serde(MethodInvocation::::SetNextBlockBaseFeePerGas( + U256::from(1), + )); } #[test] fn serde_hardhat_set_nonce() { - help_test_method_invocation_serde(MethodInvocation::SetNonce(Address::random(), 1u64)); + help_test_method_invocation_serde(MethodInvocation::::SetNonce( + Address::random(), + 1u64, + )); } #[test] fn serde_hardhat_set_prev_randao() { - help_test_method_invocation_serde(MethodInvocation::SetPrevRandao(B256::random())); + help_test_method_invocation_serde(MethodInvocation::::SetPrevRandao( + B256::random(), + )); } #[test] fn serde_hardhat_set_storage_at() { - help_test_method_invocation_serde(MethodInvocation::SetStorageAt( + help_test_method_invocation_serde(MethodInvocation::::SetStorageAt( Address::random(), U256::ZERO, U256::MAX, @@ -156,7 +173,7 @@ fn serde_hardhat_set_storage_at() { #[test] fn serde_hardhat_stop_impersonating_account() { - help_test_method_invocation_serde(MethodInvocation::StopImpersonatingAccount( + help_test_method_invocation_serde(MethodInvocation::::StopImpersonatingAccount( Address::random().into(), )); } diff --git a/crates/edr_provider/tests/issues/issue_325.rs b/crates/edr_provider/tests/issues/issue_325.rs index a8aea8290..12fa28cc9 100644 --- a/crates/edr_provider/tests/issues/issue_325.rs +++ b/crates/edr_provider/tests/issues/issue_325.rs @@ -1,12 +1,12 @@ use edr_eth::{ - chain_spec::L1ChainSpec, transaction::EthTransactionRequest, AccountInfo, Address, - PreEip1898BlockSpec, SpecId, B256, KECCAK_EMPTY, + chain_spec::L1ChainSpec, AccountInfo, Address, PreEip1898BlockSpec, SpecId, B256, KECCAK_EMPTY, }; use edr_provider::{ test_utils::{create_test_config_with_fork, one_ether}, time::CurrentTime, MethodInvocation, MiningConfig, NoopLogger, Provider, ProviderRequest, }; +use edr_rpc_eth::TransactionRequest; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -45,10 +45,10 @@ async fn issue_325() -> anyhow::Result<()> { ))?; let result = provider.handle_request(ProviderRequest::Single( - MethodInvocation::SendTransaction(EthTransactionRequest { + MethodInvocation::SendTransaction(TransactionRequest { from: impersonated_account, to: Some(Address::random()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }), ))?; diff --git a/crates/edr_provider/tests/issues/issue_326.rs b/crates/edr_provider/tests/issues/issue_326.rs index d3cb7a1c7..770ec93fb 100644 --- a/crates/edr_provider/tests/issues/issue_326.rs +++ b/crates/edr_provider/tests/issues/issue_326.rs @@ -1,15 +1,12 @@ use std::str::FromStr; -use edr_eth::{ - chain_spec::L1ChainSpec, transaction::EthTransactionRequest, AccountInfo, Address, SpecId, - KECCAK_EMPTY, U256, -}; +use edr_eth::{chain_spec::L1ChainSpec, AccountInfo, Address, SpecId, KECCAK_EMPTY, U256}; use edr_provider::{ test_utils::{create_test_config_with_fork, one_ether}, time::CurrentTime, MethodInvocation, MiningConfig, NoopLogger, Provider, ProviderRequest, }; -use edr_rpc_eth::CallRequest; +use edr_rpc_eth::{CallRequest, TransactionRequest}; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -51,12 +48,12 @@ async fn issue_326() -> anyhow::Result<()> { provider.handle_request(ProviderRequest::Single(MethodInvocation::Mine(None, None)))?; provider.handle_request(ProviderRequest::Single(MethodInvocation::SendTransaction( - EthTransactionRequest { + TransactionRequest { from: impersonated_account, to: Some(impersonated_account), nonce: Some(0), max_fee_per_gas: Some(U256::from_str("0xA").unwrap()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }, )))?; diff --git a/crates/edr_provider/tests/issues/issue_361.rs b/crates/edr_provider/tests/issues/issue_361.rs index ae1a36d51..5ccf23e6e 100644 --- a/crates/edr_provider/tests/issues/issue_361.rs +++ b/crates/edr_provider/tests/issues/issue_361.rs @@ -1,12 +1,13 @@ use edr_eth::{ - chain_spec::L1ChainSpec, filter::LogFilterOptions, transaction::EthTransactionRequest, - AccountInfo, Address, BlockSpec, SpecId, KECCAK_EMPTY, + chain_spec::L1ChainSpec, filter::LogFilterOptions, AccountInfo, Address, BlockSpec, SpecId, + KECCAK_EMPTY, }; use edr_provider::{ test_utils::{create_test_config_with_fork, one_ether}, time::CurrentTime, MethodInvocation, NoopLogger, Provider, ProviderRequest, }; +use edr_rpc_eth::TransactionRequest; use tokio::runtime; #[tokio::test(flavor = "multi_thread")] @@ -41,10 +42,10 @@ async fn issue_361() -> anyhow::Result<()> { ))?; provider.handle_request(ProviderRequest::Single(MethodInvocation::SendTransaction( - EthTransactionRequest { + TransactionRequest { from: impersonated_account, to: Some(Address::random()), - ..EthTransactionRequest::default() + ..TransactionRequest::default() }, )))?; diff --git a/crates/edr_provider/tests/timestamp.rs b/crates/edr_provider/tests/timestamp.rs index 3741aa256..43492d52e 100644 --- a/crates/edr_provider/tests/timestamp.rs +++ b/crates/edr_provider/tests/timestamp.rs @@ -11,7 +11,7 @@ use edr_provider::{ use tokio::runtime; struct TimestampFixture { - provider: Provider>, + provider: Provider>, mock_timer: Arc, } diff --git a/crates/edr_rpc_eth/src/call_request.rs b/crates/edr_rpc_eth/src/call_request.rs index 1460ff2f4..2b96065bd 100644 --- a/crates/edr_rpc_eth/src/call_request.rs +++ b/crates/edr_rpc_eth/src/call_request.rs @@ -1,4 +1,4 @@ -use edr_eth::{AccessListItem, Address, Bytes, B256, U256}; +use edr_eth::{AccessListItem, Address, Blob, Bytes, B256, U256}; /// For specifying input to methods requiring a transaction object, like /// `eth_call` and `eth_estimateGas` @@ -29,7 +29,7 @@ pub struct CallRequest { #[serde(default, rename = "type", with = "edr_eth::serde::optional_u8")] pub transaction_type: Option, /// Blobs (EIP-4844) - pub blobs: Option>, + pub blobs: Option>, /// Blob versioned hashes (EIP-4844) pub blob_hashes: Option>, } diff --git a/crates/edr_rpc_eth/src/client.rs b/crates/edr_rpc_eth/src/client.rs index 63b7d2f00..ee1b61887 100644 --- a/crates/edr_rpc_eth/src/client.rs +++ b/crates/edr_rpc_eth/src/client.rs @@ -707,7 +707,7 @@ mod tests { ); assert_eq!( tx.block_number, - Some(U256::from_str_radix("a74fde", 16).expect("couldn't parse data")) + Some(u64::from_str_radix("a74fde", 16).expect("couldn't parse data")) ); assert_eq!(tx.hash, hash); assert_eq!( diff --git a/crates/edr_rpc_eth/src/lib.rs b/crates/edr_rpc_eth/src/lib.rs index e39c52dfb..2f0548a40 100644 --- a/crates/edr_rpc_eth/src/lib.rs +++ b/crates/edr_rpc_eth/src/lib.rs @@ -1,9 +1,9 @@ -/// Types for Ethereum JSON-RPC blocks +/// Types for Ethereum JSON-RPC blocks. mod block; mod cacheable_method_invocation; -/// Input type for `eth_call` and `eth_estimateGas` +/// Input type for `eth_call` and `debug_traceCall`. mod call_request; -/// Types related to the Ethereum JSON-RPC API +/// Types related to the Ethereum JSON-RPC API. pub mod client; /// Types related to forking a remote blockchain. pub mod fork; @@ -24,5 +24,17 @@ pub use self::{ call_request::CallRequest, r#override::*, request_methods::RequestMethod, - transaction::{ConversionError as TransactionConversionError, Transaction}, + transaction::{ + ConversionError as TransactionConversionError, Transaction, TransactionRequest, + TransactionWithSignature, + }, }; + +/// Trait for constructing an RPC type from an internal type. +pub trait RpcTypeFrom { + /// The hardfork type. + type Hardfork; + + /// Constructs an RPC type from the provided internal value. + fn rpc_type_from(value: &InputT, hardfork: Self::Hardfork) -> Self; +} diff --git a/crates/edr_rpc_eth/src/receipt.rs b/crates/edr_rpc_eth/src/receipt.rs index 961193dd0..673d7e2fa 100644 --- a/crates/edr_rpc_eth/src/receipt.rs +++ b/crates/edr_rpc_eth/src/receipt.rs @@ -10,6 +10,8 @@ use edr_eth::{ }; use serde::{Deserialize, Serialize}; +use crate::RpcTypeFrom; + /// Transaction receipt #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -77,44 +79,41 @@ pub struct Block { pub authorization_list: Option>, } -pub trait ToRpcReceipt { - type Hardfork; - - fn to_rpc_receipt(&self, hardfork: Self::Hardfork) -> RpcReceiptT; -} - -impl ToRpcReceipt for receipt::BlockReceipt>> { +impl RpcTypeFrom>>> for Block { type Hardfork = SpecId; - fn to_rpc_receipt(&self, hardfork: Self::Hardfork) -> Block { + fn rpc_type_from( + value: &receipt::BlockReceipt>>, + hardfork: Self::Hardfork, + ) -> Self { let transaction_type = if hardfork >= SpecId::BERLIN { - Some(u8::from(self.inner.transaction_type())) + Some(u8::from(value.inner.transaction_type())) } else { None }; - Block { - block_hash: self.block_hash, - block_number: self.block_number, - transaction_hash: self.inner.transaction_hash, - transaction_index: self.inner.transaction_index, + Self { + block_hash: value.block_hash, + block_number: value.block_number, + transaction_hash: value.inner.transaction_hash, + transaction_index: value.inner.transaction_index, transaction_type, - from: self.inner.from, - to: self.inner.to, - cumulative_gas_used: self.inner.cumulative_gas_used(), - gas_used: self.inner.gas_used, - contract_address: self.inner.contract_address, - logs: self.inner.logs().to_vec(), - logs_bloom: *self.inner.logs_bloom(), - state_root: match self.inner.as_execution_receipt().data() { + from: value.inner.from, + to: value.inner.to, + cumulative_gas_used: value.inner.cumulative_gas_used(), + gas_used: value.inner.gas_used, + contract_address: value.inner.contract_address, + logs: value.inner.transaction_logs().to_vec(), + logs_bloom: *value.inner.logs_bloom(), + state_root: match value.inner.as_execution_receipt().data() { Execution::Legacy(receipt) => Some(receipt.root), Execution::Eip658(_) => None, }, - status: match self.inner.as_execution_receipt().data() { + status: match value.inner.as_execution_receipt().data() { Execution::Legacy(_) => None, Execution::Eip658(receipt) => Some(receipt.status), }, - effective_gas_price: self.inner.effective_gas_price, + effective_gas_price: value.inner.effective_gas_price, authorization_list: None, } } @@ -189,7 +188,9 @@ impl TryFrom for receipt::BlockReceipt TypedEnvelope::Legacy(edr_eth::receipt::Execution::Legacy(edr_eth::receipt::execution::Legacy { - root: B256::random(), - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - })), - eip658_eip2930 => TypedEnvelope::Eip2930(edr_eth::receipt::Execution::Eip658(edr_eth::receipt::execution::Eip658 { - status: true, - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - })), - eip658_eip1559 => TypedEnvelope::Eip2930(edr_eth::receipt::Execution::Eip658(edr_eth::receipt::execution::Eip658 { - status: true, - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - })), - eip658_eip4844 => TypedEnvelope::Eip4844(edr_eth::receipt::Execution::Eip658(edr_eth::receipt::execution::Eip658 { - status: true, - cumulative_gas_used: 0xffff, - logs_bloom: Bloom::random(), - logs: vec![ - ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), - ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) - ], - })), + L1ChainSpec => { + legacy => TypedEnvelope::Legacy(edr_eth::receipt::Execution::Legacy(edr_eth::receipt::execution::Legacy { + root: B256::random(), + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + })), + eip658_eip2930 => TypedEnvelope::Eip2930(edr_eth::receipt::Execution::Eip658(edr_eth::receipt::execution::Eip658 { + status: true, + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + })), + eip658_eip1559 => TypedEnvelope::Eip2930(edr_eth::receipt::Execution::Eip658(edr_eth::receipt::execution::Eip658 { + status: true, + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + })), + eip658_eip4844 => TypedEnvelope::Eip4844(edr_eth::receipt::Execution::Eip658(edr_eth::receipt::execution::Eip658 { + status: true, + cumulative_gas_used: 0xffff, + logs_bloom: Bloom::random(), + logs: vec![ + ExecutionLog::new_unchecked(Address::random(), vec![B256::random(), B256::random()], Bytes::new()), + ExecutionLog::new_unchecked(Address::random(), Vec::new(), Bytes::from_static(b"test")) + ], + })), + } } } diff --git a/crates/edr_rpc_eth/src/spec.rs b/crates/edr_rpc_eth/src/spec.rs index 4ff1de8ab..86f461dc3 100644 --- a/crates/edr_rpc_eth/src/spec.rs +++ b/crates/edr_rpc_eth/src/spec.rs @@ -1,7 +1,7 @@ use edr_eth::{chain_spec::L1ChainSpec, eips::eip2718::TypedEnvelope, receipt::Receipt}; use serde::{de::DeserializeOwned, Serialize}; -use crate::receipt::Block; +use crate::{receipt::Block, CallRequest}; /// Trait for specifying Ethereum-based JSON-RPC method types. pub trait RpcSpec: Sized { @@ -13,11 +13,17 @@ pub trait RpcSpec: Sized { where Data: Default + DeserializeOwned + Serialize; + /// Type representing an RPC `eth_call` request. + type RpcCallRequest: DeserializeOwned + Serialize; + /// Type representing an RPC receipt. type RpcReceipt: DeserializeOwned + Serialize; /// Type representing an RPC transaction. type RpcTransaction: Default + DeserializeOwned + Serialize; + + /// Type representing an RPC `eth_sendTransaction` request. + type RpcTransactionRequest: DeserializeOwned + Serialize; } pub trait GetBlockNumber { @@ -27,6 +33,8 @@ pub trait GetBlockNumber { impl RpcSpec for L1ChainSpec { type ExecutionReceipt = TypedEnvelope>; type RpcBlock = crate::block::Block where Data: Default + DeserializeOwned + Serialize; + type RpcCallRequest = CallRequest; type RpcReceipt = Block; - type RpcTransaction = crate::transaction::Transaction; + type RpcTransaction = crate::transaction::TransactionWithSignature; + type RpcTransactionRequest = crate::transaction::TransactionRequest; } diff --git a/crates/edr_rpc_eth/src/test_utils.rs b/crates/edr_rpc_eth/src/test_utils.rs index c102bde0e..c74d4243c 100644 --- a/crates/edr_rpc_eth/src/test_utils.rs +++ b/crates/edr_rpc_eth/src/test_utils.rs @@ -1,8 +1,10 @@ #[macro_export] macro_rules! impl_execution_receipt_tests { - ($( - $name:ident => $receipt:expr, - )+) => { + ($chain_spec:ident => { + $( + $name:ident => $receipt:expr, + )+ + }) => { $( paste::item! { #[test] @@ -15,7 +17,7 @@ macro_rules! impl_execution_receipt_tests { Address, B256, U256, }; - use $crate::receipt::ToRpcReceipt as _; + use $crate::{RpcTypeFrom as _, spec::RpcSpec}; let block_hash = B256::random(); let block_number = 10u64; @@ -61,7 +63,7 @@ macro_rules! impl_execution_receipt_tests { block_number, }; - let rpc_receipt = block_receipt.to_rpc_receipt(Default::default()); + let rpc_receipt = <$chain_spec as RpcSpec>::RpcReceipt::rpc_type_from(&block_receipt, Default::default()); let serialized = serde_json::to_string(&rpc_receipt)?; let deserialized = serde_json::from_str(&serialized)?; diff --git a/crates/edr_rpc_eth/src/transaction.rs b/crates/edr_rpc_eth/src/transaction.rs index 1c3395a06..7de403351 100644 --- a/crates/edr_rpc_eth/src/transaction.rs +++ b/crates/edr_rpc_eth/src/transaction.rs @@ -1,11 +1,17 @@ -use std::sync::OnceLock; +mod request; + +use std::{ops::Deref, sync::OnceLock}; use edr_eth::{ - signature, - transaction::{self, TxKind}, - AccessListItem, Address, Bytes, B256, U256, + block, signature, + transaction::{ + self, ExecutableTransaction, HasAccessList, IsEip4844, IsLegacy, TransactionType, TxKind, + }, + AccessListItem, Address, Bytes, SpecId, B256, U256, }; +pub use self::request::TransactionRequest; + /// RPC transaction #[derive(Clone, Debug, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] @@ -13,15 +19,16 @@ pub struct Transaction { /// hash of the transaction pub hash: B256, /// the number of transactions made by the sender prior to this one - #[serde(with = "edr_eth::serde::u64")] + #[serde(with = "alloy_serde::quantity")] pub nonce: u64, /// hash of the block where this transaction was in pub block_hash: Option, /// block number where this transaction was in - pub block_number: Option, + #[serde(with = "alloy_serde::quantity::opt")] + pub block_number: Option, /// integer of the transactions index position in the block. null when its /// pending - #[serde(with = "edr_eth::serde::optional_u64")] + #[serde(with = "alloy_serde::quantity::opt")] pub transaction_index: Option, /// address of the sender pub from: Address, @@ -35,28 +42,11 @@ pub struct Transaction { pub gas: U256, /// the data sent along with the transaction pub input: Bytes, - /// ECDSA recovery id - #[serde(with = "edr_eth::serde::u64")] - pub v: u64, - /// Y-parity for EIP-2930 and EIP-1559 transactions. In theory these - /// transactions types shouldn't have a `v` field, but in practice they - /// are returned by nodes. - #[serde( - default, - rename = "yParity", - skip_serializing_if = "Option::is_none", - with = "edr_eth::serde::optional_u64" - )] - pub y_parity: Option, - /// ECDSA signature r - pub r: U256, - /// ECDSA signature s - pub s: U256, /// chain ID #[serde( default, skip_serializing_if = "Option::is_none", - with = "edr_eth::serde::optional_u64" + with = "alloy_serde::quantity::opt" )] pub chain_id: Option, /// integer of the transaction type, 0x0 for legacy transactions, 0x1 for @@ -65,7 +55,7 @@ pub struct Transaction { rename = "type", default, skip_serializing_if = "Option::is_none", - with = "edr_eth::serde::optional_u8" + with = "alloy_serde::quantity::opt" )] pub transaction_type: Option, /// access list @@ -88,6 +78,142 @@ pub struct Transaction { } impl Transaction { + pub fn new( + transaction: &(impl ExecutableTransaction + + edr_eth::transaction::Transaction + + TransactionType + + HasAccessList + + IsEip4844 + + IsLegacy), + header: Option<&block::Header>, + transaction_index: Option, + is_pending: bool, + hardfork: SpecId, + ) -> Self { + let base_fee = header.and_then(|header| header.base_fee_per_gas); + let gas_price = if let Some(base_fee) = base_fee { + transaction + .effective_gas_price(base_fee) + .unwrap_or_else(|| *transaction.gas_price()) + } else { + // We are following Hardhat's behavior of returning the max fee per gas for + // pending transactions. + *transaction.gas_price() + }; + + let chain_id = transaction.chain_id().and_then(|chain_id| { + // Following Hardhat in not returning `chain_id` for `PostEip155Legacy` legacy + // transactions even though the chain id would be recoverable. + if transaction.is_legacy() { + None + } else { + Some(chain_id) + } + }); + + let show_transaction_type = hardfork >= SpecId::BERLIN; + let is_typed_transaction = !transaction.is_legacy(); + let transaction_type = if show_transaction_type || is_typed_transaction { + Some(transaction.transaction_type()) + } else { + None + }; + + let (block_hash, block_number) = if is_pending { + (None, None) + } else { + header.map(|header| (header.hash(), header.number)).unzip() + }; + + let transaction_index = if is_pending { None } else { transaction_index }; + + let access_list = if transaction.has_access_list() { + Some(transaction.access_list().to_vec()) + } else { + None + }; + + let blob_versioned_hashes = if transaction.is_eip4844() { + Some(transaction.blob_hashes().to_vec()) + } else { + None + }; + + Self { + hash: *transaction.transaction_hash(), + nonce: transaction.nonce(), + block_hash, + block_number, + transaction_index, + from: *transaction.caller(), + to: transaction.kind().to().copied(), + value: *transaction.value(), + gas_price, + gas: U256::from(transaction.gas_limit()), + input: transaction.data().clone(), + chain_id, + transaction_type: transaction_type.map(Into::::into), + access_list, + max_fee_per_gas: transaction.max_fee_per_gas().copied(), + max_priority_fee_per_gas: transaction.max_priority_fee_per_gas().cloned(), + max_fee_per_blob_gas: transaction.max_fee_per_blob_gas().cloned(), + blob_versioned_hashes, + } + } +} + +/// RPC transaction with signature. +#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionWithSignature { + /// Transaction + #[serde(flatten)] + transaction: Transaction, + /// ECDSA recovery id + #[serde(with = "alloy_serde::quantity")] + pub v: u64, + /// Y-parity for EIP-2930 and EIP-1559 transactions. In theory these + /// transactions types shouldn't have a `v` field, but in practice they + /// are returned by nodes. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + )] + pub y_parity: Option, + /// ECDSA signature r + pub r: U256, + /// ECDSA signature s + pub s: U256, +} + +impl Deref for TransactionWithSignature { + type Target = Transaction; + + fn deref(&self) -> &Self::Target { + &self.transaction + } +} + +impl TransactionWithSignature { + /// Creates a new instance from an RPC transaction and signature. + pub fn new( + transaction: Transaction, + r: U256, + s: U256, + v: u64, + _y_parity: Option, + ) -> Self { + Self { + transaction, + v, + // Following Hardhat in always returning `v` instead of `y_parity`. + y_parity: None, + r, + s, + } + } + /// Returns whether the transaction has odd Y parity. pub fn odd_y_parity(&self) -> bool { self.v == 1 || self.v == 28 @@ -99,8 +225,8 @@ impl Transaction { } } -impl From for transaction::signed::Legacy { - fn from(value: Transaction) -> Self { +impl From for transaction::signed::Legacy { + fn from(value: TransactionWithSignature) -> Self { Self { nonce: value.nonce, gas_price: value.gas_price, @@ -111,7 +237,7 @@ impl From for transaction::signed::Legacy { TxKind::Create }, value: value.value, - input: value.input, + input: value.transaction.input, // SAFETY: The `from` field represents the caller address of the signed // transaction. signature: unsafe { @@ -121,17 +247,17 @@ impl From for transaction::signed::Legacy { s: value.s, v: value.v, }, - value.from, + value.transaction.from, ) }, - hash: OnceLock::from(value.hash), + hash: OnceLock::from(value.transaction.hash), rlp_encoding: OnceLock::new(), } } } -impl From for transaction::signed::Eip155 { - fn from(value: Transaction) -> Self { +impl From for transaction::signed::Eip155 { + fn from(value: TransactionWithSignature) -> Self { Self { nonce: value.nonce, gas_price: value.gas_price, @@ -142,7 +268,7 @@ impl From for transaction::signed::Eip155 { TxKind::Create }, value: value.value, - input: value.input, + input: value.transaction.input, // SAFETY: The `from` field represents the caller address of the signed // transaction. signature: unsafe { @@ -152,19 +278,19 @@ impl From for transaction::signed::Eip155 { s: value.s, v: value.v, }, - value.from, + value.transaction.from, ) }, - hash: OnceLock::from(value.hash), + hash: OnceLock::from(value.transaction.hash), rlp_encoding: OnceLock::new(), } } } -impl TryFrom for transaction::signed::Eip2930 { +impl TryFrom for transaction::signed::Eip2930 { type Error = ConversionError; - fn try_from(value: Transaction) -> Result { + fn try_from(value: TransactionWithSignature) -> Result { let transaction = Self { // SAFETY: The `from` field represents the caller address of the signed // transaction. @@ -188,9 +314,13 @@ impl TryFrom for transaction::signed::Eip2930 { TxKind::Create }, value: value.value, - input: value.input, - access_list: value.access_list.ok_or(ConversionError::AccessList)?.into(), - hash: OnceLock::from(value.hash), + input: value.transaction.input, + access_list: value + .transaction + .access_list + .ok_or(ConversionError::AccessList)? + .into(), + hash: OnceLock::from(value.transaction.hash), rlp_encoding: OnceLock::new(), }; @@ -198,10 +328,10 @@ impl TryFrom for transaction::signed::Eip2930 { } } -impl TryFrom for transaction::signed::Eip1559 { +impl TryFrom for transaction::signed::Eip1559 { type Error = ConversionError; - fn try_from(value: Transaction) -> Result { + fn try_from(value: TransactionWithSignature) -> Result { let transaction = Self { // SAFETY: The `from` field represents the caller address of the signed // transaction. @@ -228,9 +358,13 @@ impl TryFrom for transaction::signed::Eip1559 { TxKind::Create }, value: value.value, - input: value.input, - access_list: value.access_list.ok_or(ConversionError::AccessList)?.into(), - hash: OnceLock::from(value.hash), + input: value.transaction.input, + access_list: value + .transaction + .access_list + .ok_or(ConversionError::AccessList)? + .into(), + hash: OnceLock::from(value.transaction.hash), rlp_encoding: OnceLock::new(), }; @@ -238,10 +372,10 @@ impl TryFrom for transaction::signed::Eip1559 { } } -impl TryFrom for transaction::signed::Eip4844 { +impl TryFrom for transaction::signed::Eip4844 { type Error = ConversionError; - fn try_from(value: Transaction) -> Result { + fn try_from(value: TransactionWithSignature) -> Result { let transaction = Self { // SAFETY: The `from` field represents the caller address of the signed // transaction. @@ -267,12 +401,17 @@ impl TryFrom for transaction::signed::Eip4844 { gas_limit: value.gas.to(), to: value.to.ok_or(ConversionError::ReceiverAddress)?, value: value.value, - input: value.input, - access_list: value.access_list.ok_or(ConversionError::AccessList)?.into(), + input: value.transaction.input, + access_list: value + .transaction + .access_list + .ok_or(ConversionError::AccessList)? + .into(), blob_hashes: value + .transaction .blob_versioned_hashes .ok_or(ConversionError::BlobHashes)?, - hash: OnceLock::from(value.hash), + hash: OnceLock::from(value.transaction.hash), rlp_encoding: OnceLock::new(), }; @@ -280,10 +419,10 @@ impl TryFrom for transaction::signed::Eip4844 { } } -impl TryFrom for transaction::Signed { +impl TryFrom for transaction::Signed { type Error = ConversionError; - fn try_from(value: Transaction) -> Result { + fn try_from(value: TransactionWithSignature) -> Result { let transaction_type = match value .transaction_type .map_or(Ok(transaction::Type::Legacy), transaction::Type::try_from) diff --git a/crates/edr_rpc_eth/src/transaction/request.rs b/crates/edr_rpc_eth/src/transaction/request.rs new file mode 100644 index 000000000..151950dc0 --- /dev/null +++ b/crates/edr_rpc_eth/src/transaction/request.rs @@ -0,0 +1,45 @@ +use edr_eth::{AccessListItem, Address, Blob, Bytes, B256, U256}; + +/// Represents _all_ transaction requests received from RPC +#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionRequest { + /// from address + pub from: Address, + /// to address + #[serde(default)] + pub to: Option
, + /// legacy, gas Price + #[serde(default)] + pub gas_price: Option, + /// max base fee per gas sender is willing to pay + #[serde(default)] + pub max_fee_per_gas: Option, + /// miner tip + #[serde(default)] + pub max_priority_fee_per_gas: Option, + /// gas + #[serde(default, with = "alloy_serde::quantity::opt")] + pub gas: Option, + /// value of th tx in wei + pub value: Option, + /// Any additional data sent + #[serde(alias = "input")] + pub data: Option, + /// Transaction nonce + #[serde(default, with = "alloy_serde::quantity::opt")] + pub nonce: Option, + /// Chain ID + #[serde(default, with = "alloy_serde::quantity::opt")] + pub chain_id: Option, + /// warm storage access pre-payment + #[serde(default)] + pub access_list: Option>, + /// EIP-2718 type + #[serde(default, rename = "type", with = "alloy_serde::quantity::opt")] + pub transaction_type: Option, + /// Blobs (EIP-4844) + pub blobs: Option>, + /// Blob versioned hashes (EIP-4844) + pub blob_hashes: Option>, +} diff --git a/crates/tools/src/scenario.rs b/crates/tools/src/scenario.rs index 1b1b5ec1a..50d4a6553 100644 --- a/crates/tools/src/scenario.rs +++ b/crates/tools/src/scenario.rs @@ -1,3 +1,4 @@ +use core::fmt::Debug; use std::{ marker::PhantomData, path::{Path, PathBuf}, @@ -104,7 +105,7 @@ pub async fn execute(scenario_path: &Path, max_count: Option) -> anyhow:: async fn load_requests( scenario_path: &Path, -) -> anyhow::Result<(ScenarioConfig, Vec)> { +) -> anyhow::Result<(ScenarioConfig, Vec>)> { println!("Loading requests from {scenario_path:?}"); match load_gzipped_json(scenario_path.to_path_buf()).await { @@ -116,7 +117,7 @@ async fn load_requests( async fn load_gzipped_json( scenario_path: PathBuf, -) -> anyhow::Result<(ScenarioConfig, Vec)> { +) -> anyhow::Result<(ScenarioConfig, Vec>)> { use std::{ fs::File, io::{BufRead, BufReader}, @@ -135,11 +136,11 @@ async fn load_gzipped_json( .context("Invalid gzip")?; let config: ScenarioConfig = serde_json::from_str(&first_line)?; - let mut requests: Vec = Vec::new(); + let mut requests: Vec> = Vec::new(); for gzipped_line in lines { let line = gzipped_line.context("Invalid gzip")?; - let request: ProviderRequest = serde_json::from_str(&line)?; + let request: ProviderRequest = serde_json::from_str(&line)?; requests.push(request); } @@ -148,7 +149,9 @@ async fn load_gzipped_json( .await? } -async fn load_json(scenario_path: &Path) -> anyhow::Result<(ScenarioConfig, Vec)> { +async fn load_json( + scenario_path: &Path, +) -> anyhow::Result<(ScenarioConfig, Vec>)> { use tokio::io::AsyncBufReadExt; let reader = tokio::io::BufReader::new(tokio::fs::File::open(scenario_path).await?); @@ -157,10 +160,10 @@ async fn load_json(scenario_path: &Path) -> anyhow::Result<(ScenarioConfig, Vec< let first_line = lines.next_line().await?.context("Scenario file is empty")?; let config: ScenarioConfig = serde_json::from_str(&first_line)?; - let mut requests: Vec = Vec::new(); + let mut requests: Vec> = Vec::new(); while let Some(line) = lines.next_line().await? { - let request: ProviderRequest = serde_json::from_str(&line)?; + let request: ProviderRequest = serde_json::from_str(&line)?; requests.push(request); } @@ -172,7 +175,7 @@ struct DisabledLogger { _phantom: PhantomData, } -impl Logger for DisabledLogger { +impl> Logger for DisabledLogger { type BlockchainError = BlockchainError; fn is_enabled(&self) -> bool { @@ -184,7 +187,7 @@ impl Logger for DisabledLogger { fn print_method_logs( &mut self, _method: &str, - _error: Option<&ProviderError>, + _error: Option<&ProviderError>, ) -> Result<(), Box> { Ok(()) } diff --git a/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/call.ts b/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/call.ts index 5740a86f7..ccecd6267 100644 --- a/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/call.ts +++ b/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/call.ts @@ -1,3 +1,4 @@ +import { bytesToHex as bufferToHex } from "@nomicfoundation/ethereumjs-util"; import { assert } from "chai"; import { ethers } from "ethers"; import { @@ -1611,7 +1612,7 @@ contract C { { from: DEFAULT_ACCOUNTS_ADDRESSES[1], to: DEFAULT_ACCOUNTS_ADDRESSES[2], - blobs: ["0x1234"], + blobs: [bufferToHex(new Uint8Array(131072))], blobVersionedHashes: [ "0x1234567812345678123456781234567812345678123456781234567812345678", ], diff --git a/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/estimateGas.ts b/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/estimateGas.ts index 8f6be6a06..bf24f8f41 100644 --- a/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/estimateGas.ts +++ b/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/estimateGas.ts @@ -1,4 +1,7 @@ -import { zeroAddress } from "@nomicfoundation/ethereumjs-util"; +import { + zeroAddress, + bytesToHex as bufferToHex, +} from "@nomicfoundation/ethereumjs-util"; import { assert } from "chai"; import { Client } from "undici"; @@ -208,7 +211,7 @@ describe("Eth module", function () { { from: DEFAULT_ACCOUNTS_ADDRESSES[1], to: DEFAULT_ACCOUNTS_ADDRESSES[2], - blobs: ["0x1234"], + blobs: [bufferToHex(new Uint8Array(131072))], blobVersionedHashes: [ "0x1234567812345678123456781234567812345678123456781234567812345678", ], diff --git a/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/sendTransaction.ts b/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/sendTransaction.ts index c287a5a89..d6ed7a660 100644 --- a/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/sendTransaction.ts +++ b/hardhat-tests/test/internal/hardhat-network/provider/modules/eth/methods/sendTransaction.ts @@ -1,4 +1,7 @@ -import { zeroAddress } from "@nomicfoundation/ethereumjs-util"; +import { + bytesToHex as bufferToHex, + zeroAddress, +} from "@nomicfoundation/ethereumjs-util"; import { assert } from "chai"; import { Client } from "undici"; import { @@ -1152,7 +1155,7 @@ describe("Eth module", function () { { from: DEFAULT_ACCOUNTS_ADDRESSES[1], to: DEFAULT_ACCOUNTS_ADDRESSES[2], - blobs: ["0x1234"], + blobs: [bufferToHex(new Uint8Array(131072))], blobVersionedHashes: [ "0x1234567812345678123456781234567812345678123456781234567812345678", ],