Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: extract rpc<>revm specifc code #1818

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions crates/interfaces/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,13 @@ pub enum Error {

#[error(transparent)]
Network(#[from] reth_network_api::NetworkError),

#[error(transparent)]
Revm(#[from] reth_rpc_types::error::RevmError),
}

impl From<Error> for reth_rpc_types::error::RevmError {
fn from(_: Error) -> Self {
Self::Internal
}
}
mattsse marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions crates/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ reth-codecs = { version = "0.1.0", path = "../storage/codecs" }
revm-primitives = { version = "1.0", features = ["serde"] }

# ethereum
revm = { version = "3.0.0", features = ["optional_block_gas_limit"] }
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }
tiny-keccak = { version = "2.0", features = ["keccak"] }
crunchy = { version = "0.2.2", default-features = false, features = ["limit_256"] }
Expand Down
44 changes: 44 additions & 0 deletions crates/primitives/src/transaction/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,48 @@ pub enum InvalidTransactionError {
/// Thrown if the sender of a transaction is a contract.
#[error("Transaction signer has bytecode set.")]
SignerAccountHasBytecode,

#[error("MaxInitCodeSizeExceeded.")]
MaxInitCodeSizeExceeded,

#[error("nonce too low")]
NonceTooLow,

#[error("Nonce too high")]
NonceTooHigh,
}

impl From<revm::primitives::InvalidTransaction> for InvalidTransactionError {
fn from(err: revm::primitives::InvalidTransaction) -> Self {
use revm::primitives::InvalidTransaction;
match err {
InvalidTransaction::InvalidChainId => InvalidTransactionError::ChainIdMismatch,
InvalidTransaction::GasMaxFeeGreaterThanPriorityFee => {
InvalidTransactionError::TipAboveFeeCap
}
InvalidTransaction::GasPriceLessThanBasefee => InvalidTransactionError::FeeCapTooLow,
InvalidTransaction::CallerGasLimitMoreThanBlock => InvalidTransactionError::GasTooHigh,
InvalidTransaction::CallGasCostMoreThanGasLimit => InvalidTransactionError::GasTooHigh,
InvalidTransaction::RejectCallerWithCode => {
InvalidTransactionError::SignerAccountHasBytecode
}
InvalidTransaction::LackOfFundForGasLimit { .. } => {
InvalidTransactionError::InsufficientFunds {
max_fee: 0,
available_funds: U256::from(0),
}
}
InvalidTransaction::OverflowPaymentInTransaction => {
InvalidTransactionError::GasUintOverflow
}
InvalidTransaction::NonceOverflowInTransaction => {
InvalidTransactionError::NonceNotConsistent
}
InvalidTransaction::CreateInitcodeSizeLimit => {
InvalidTransactionError::MaxInitCodeSizeExceeded
}
InvalidTransaction::NonceTooHigh { .. } => InvalidTransactionError::NonceTooHigh,
InvalidTransaction::NonceTooLow { .. } => InvalidTransactionError::NonceTooLow,
}
}
}
3 changes: 3 additions & 0 deletions crates/rpc/rpc-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ reth-primitives = { path = "../../primitives" }
reth-rlp = { path = "../../rlp" }
reth-network-api = { path = "../../net/network-api"}

# eth
revm = { version = "3.0.0", features = ["optional_block_gas_limit"] }

# for geth tracing types
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }

Expand Down
173 changes: 172 additions & 1 deletion crates/rpc/rpc-types/src/eth/call.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
use reth_primitives::{AccessList, Address, Bytes, U256, U64};
use crate::eth::error::{RevmError, RevmResult};
use reth_primitives::{AccessList, Address, Bytes, InvalidTransactionError, U256, U64};
use revm::{
precompile::{Precompiles, SpecId as PrecompilesSpecId},
primitives::{BlockEnv, CfgEnv, Env, ResultAndState, SpecId, TransactTo, TxEnv},
Database, Inspector,
};
use serde::{Deserialize, Serialize};

/// Call request
Expand Down Expand Up @@ -28,3 +34,168 @@ pub struct CallRequest {
/// AccessList
pub access_list: Option<AccessList>,
}

/// Returns the addresses of the precompiles corresponding to the SpecId.
pub fn get_precompiles(spec_id: &SpecId) -> Vec<reth_primitives::H160> {
let spec = match spec_id {
SpecId::FRONTIER | SpecId::FRONTIER_THAWING => return vec![],
SpecId::HOMESTEAD | SpecId::DAO_FORK | SpecId::TANGERINE | SpecId::SPURIOUS_DRAGON => {
PrecompilesSpecId::HOMESTEAD
}
SpecId::BYZANTIUM | SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => {
PrecompilesSpecId::BYZANTIUM
}
SpecId::ISTANBUL | SpecId::MUIR_GLACIER => PrecompilesSpecId::ISTANBUL,
SpecId::BERLIN |
SpecId::LONDON |
SpecId::ARROW_GLACIER |
SpecId::GRAY_GLACIER |
SpecId::MERGE |
SpecId::SHANGHAI |
SpecId::CANCUN => PrecompilesSpecId::BERLIN,
SpecId::LATEST => PrecompilesSpecId::LATEST,
};
Precompiles::new(spec).addresses().into_iter().map(Address::from).collect()
}

/// Executes the [Env] against the given [Database] without committing state changes.
pub fn transact<S>(db: S, env: Env) -> RevmResult<(ResultAndState, Env)>
where
S: Database,
<S as Database>::Error: Into<RevmError>,
{
let mut evm = revm::EVM::with_env(env);
evm.database(db);
let res = evm.transact()?;
Ok((res, evm.env))
}

/// Executes the [Env] against the given [Database] without committing state changes.
pub fn inspect<S, I>(db: S, env: Env, inspector: I) -> RevmResult<(ResultAndState, Env)>
where
S: Database,
<S as Database>::Error: Into<RevmError>,
I: Inspector<S>,
{
let mut evm = revm::EVM::with_env(env);
evm.database(db);
let res = evm.inspect(inspector)?;
Ok((res, evm.env))
}

/// Creates a new [Env] to be used for executing the [CallRequest] in `eth_call`
pub fn build_call_evm_env(cfg: CfgEnv, block: BlockEnv, request: CallRequest) -> RevmResult<Env> {
let tx = create_txn_env(&block, request)?;
Ok(Env { cfg, block, tx })
}

/// Configures a new [TxEnv] for the [CallRequest]
pub fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> RevmResult<TxEnv> {
let CallRequest {
from,
to,
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
gas,
value,
data,
nonce,
access_list,
chain_id,
} = request;

let CallFees { max_priority_fee_per_gas, gas_price } = CallFees::ensure_fees(
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
block_env.basefee,
)?;

let gas_limit = gas.unwrap_or(block_env.gas_limit.min(U256::from(u64::MAX)));

let env = TxEnv {
gas_limit: gas_limit.try_into().map_err(|_| InvalidTransactionError::GasUintOverflow)?,
nonce: nonce
.map(|n| n.try_into().map_err(|_| InvalidTransactionError::NonceTooHigh))
.transpose()?,
caller: from.unwrap_or_default(),
gas_price,
gas_priority_fee: max_priority_fee_per_gas,
transact_to: to.map(TransactTo::Call).unwrap_or_else(TransactTo::create),
value: value.unwrap_or_default(),
data: data.map(|data| data.0).unwrap_or_default(),
chain_id: chain_id.map(|c| c.as_u64()),
access_list: access_list.map(AccessList::flattened).unwrap_or_default(),
};

Ok(env)
}

/// Helper type for representing the fees of a [CallRequest]
pub(crate) struct CallFees {
/// EIP-1559 priority fee
max_priority_fee_per_gas: Option<U256>,
/// Unified gas price setting
///
/// Will be the configured `basefee` if unset in the request
///
/// `gasPrice` for legacy,
/// `maxFeePerGas` for EIP-1559
gas_price: U256,
}

// === impl CallFees ===

impl CallFees {
/// Ensures the fields of a [CallRequest] are not conflicting.
///
/// If no `gasPrice` or `maxFeePerGas` is set, then the `gas_price` in the response will
/// fallback to the given `basefee`.
fn ensure_fees(
call_gas_price: Option<U256>,
call_max_fee: Option<U256>,
call_priority_fee: Option<U256>,
base_fee: U256,
) -> RevmResult<CallFees> {
match (call_gas_price, call_max_fee, call_priority_fee) {
(gas_price, None, None) => {
// request for a legacy transaction
// set everything to zero
let gas_price = gas_price.unwrap_or(base_fee);
Ok(CallFees { gas_price, max_priority_fee_per_gas: None })
}
(None, max_fee_per_gas, max_priority_fee_per_gas) => {
// request for eip-1559 transaction
let max_fee = max_fee_per_gas.unwrap_or(base_fee);

if let Some(max_priority) = max_priority_fee_per_gas {
if max_priority > max_fee {
// Fail early
return Err(
// `max_priority_fee_per_gas` is greater than the `max_fee_per_gas`
InvalidTransactionError::TipAboveFeeCap.into(),
)
}
}
Ok(CallFees { gas_price: max_fee, max_priority_fee_per_gas })
}
(Some(gas_price), Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => {
Err(RevmError::ConflictingRequestGasPriceAndTipSet {
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
})
}
(Some(gas_price), Some(max_fee_per_gas), None) => {
Err(RevmError::ConflictingRequestGasPrice { gas_price, max_fee_per_gas })
}
(Some(gas_price), None, Some(max_priority_fee_per_gas)) => {
Err(RevmError::RequestLegacyGasPriceAndTipSet {
gas_price,
max_priority_fee_per_gas,
})
}
}
}
}
54 changes: 54 additions & 0 deletions crates/rpc/rpc-types/src/eth/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
//! Commonly used errors for the `eth_` namespace.

use reth_primitives::{InvalidTransactionError, U256};
use revm::primitives::EVMError;

/// Result alias
pub type RevmResult<T> = Result<T, RevmError>;

/// List of JSON-RPC error codes
#[derive(Debug, Copy, PartialEq, Eq, Clone)]
pub enum EthRpcErrorCode {
Expand All @@ -21,3 +27,51 @@ impl EthRpcErrorCode {
}
}
}

#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum RevmError {
Copy link
Member

@onbjerg onbjerg Mar 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should rly be using the executor error enum instead of writing its own:

pub enum Error {

And the functions like create_txn_env are handled by the executor interface - if it is not good enough, it should be adjusted. Ideally the RPC would use an ExecutorFactory like the rest of the code

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the RPC would use an ExecutorFactory like the rest of the code

RPC does not write to Disk and needs to configure the env directly depending on the RPC call.

The EvmEnvProvider trait is used by RPC to get block and cfg of the env and create_txn_env configures the txenv based on the request.

/// When a raw transaction is empty
#[error("Empty transaction data")]
EmptyRawTransactionData,
#[error("Failed to decode signed transaction")]
FailedToDecodeSignedTransaction,
#[error("Invalid transaction signature")]
InvalidTransactionSignature,
#[error("PoolError")]
PoolError,
#[error("Unknown block number")]
UnknownBlockNumber,
#[error("Invalid block range")]
InvalidBlockRange,
/// An internal error where prevrandao is not set in the evm's environment
#[error("Prevrandao not in th EVM's environment after merge")]
PrevrandaoNotSet,
#[error("Invalid transaction")]
InvalidTransaction(#[from] InvalidTransactionError),
#[error("Conflicting fee values in request. Both legacy gasPrice {gas_price} and maxFeePerGas {max_fee_per_gas} set")]
ConflictingRequestGasPrice { gas_price: U256, max_fee_per_gas: U256 },
#[error("Conflicting fee values in request. Both legacy gasPrice {gas_price} maxFeePerGas {max_fee_per_gas} and maxPriorityFeePerGas {max_priority_fee_per_gas} set")]
ConflictingRequestGasPriceAndTipSet {
gas_price: U256,
max_fee_per_gas: U256,
max_priority_fee_per_gas: U256,
},
#[error("Conflicting fee values in request. Legacy gasPrice {gas_price} and maxPriorityFeePerGas {max_priority_fee_per_gas} set")]
RequestLegacyGasPriceAndTipSet { gas_price: U256, max_priority_fee_per_gas: U256 },
#[error("Unknown error")]
Internal,
}

impl<T> From<EVMError<T>> for RevmError
where
T: Into<RevmError>,
{
fn from(err: EVMError<T>) -> Self {
match err {
EVMError::Transaction(err) => InvalidTransactionError::from(err).into(),
EVMError::PrevrandaoNotSet => RevmError::PrevrandaoNotSet,
EVMError::Database(err) => err.into(),
}
}
}
4 changes: 3 additions & 1 deletion crates/rpc/rpc-types/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ mod work;

pub use account::*;
pub use block::*;
pub use call::CallRequest;
pub use call::{
build_call_evm_env, create_txn_env, get_precompiles, inspect, transact, CallRequest,
};
pub use fee::{FeeHistory, FeeHistoryCache, FeeHistoryCacheItem};
pub use filter::*;
pub use index::Index;
Expand Down
6 changes: 3 additions & 3 deletions crates/rpc/rpc/src/eth/api/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use crate::{
eth::{
error::{EthApiError, EthResult, InvalidTransactionError, RevertError},
revm_utils::{build_call_evm_env, get_precompiles, inspect, transact},
EthTransactions,
},
EthApi,
Expand All @@ -16,8 +15,9 @@ use reth_revm::{
database::{State, SubState},
};
use reth_rpc_types::{
build_call_evm_env, get_precompiles, inspect,
state::{AccountOverride, StateOverride},
CallRequest,
transact, CallRequest,
};
use reth_transaction_pool::TransactionPool;
use revm::{
Expand Down Expand Up @@ -76,7 +76,7 @@ where
apply_state_overrides(state_overrides, &mut db)?;
}

transact(&mut db, env)
Ok(transact(&mut db, env)?)
}

/// Estimate gas needed for execution of the `request` at the [BlockId].
Expand Down
Loading