From 77d108660e78660d32aa3a35fc664fd5fbfdf985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 8 Mar 2023 06:07:09 -0300 Subject: [PATCH] feat(rpc): add `eth_createAccessList` implementation (#1652) Co-authored-by: lambdaclass-user --- Cargo.lock | 1 + crates/primitives/src/lib.rs | 7 +- .../primitives/src/transaction/access_list.rs | 11 ++ crates/primitives/src/transaction/mod.rs | 2 +- crates/revm/Cargo.toml | 3 +- crates/revm/src/lib.rs | 2 + crates/rpc/rpc-api/src/eth.rs | 3 +- crates/rpc/rpc-builder/tests/it/http.rs | 5 +- crates/rpc/rpc-types/src/eth/log.rs | 1 - .../rpc/rpc-types/src/eth/transaction/mod.rs | 1 - crates/rpc/rpc/Cargo.toml | 7 +- crates/rpc/rpc/src/eth/api/call.rs | 115 ++++++++++++++++-- crates/rpc/rpc/src/eth/api/server.rs | 13 +- crates/storage/provider/src/providers/mod.rs | 1 + 14 files changed, 139 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c91026d0fb..595da4031f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4868,6 +4868,7 @@ dependencies = [ "reth-interfaces", "reth-primitives", "reth-provider", + "reth-revm-inspectors", "reth-revm-primitives", "revm", ] diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 3b0e600ea7..52f349c3af 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -59,9 +59,10 @@ pub use receipt::Receipt; pub use serde_helper::JsonU256; pub use storage::{StorageEntry, StorageTrieEntry}; pub use transaction::{ - AccessList, AccessListItem, FromRecoveredTransaction, IntoRecoveredTransaction, Signature, - Transaction, TransactionKind, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, - TxEip2930, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, + AccessList, AccessListItem, AccessListWithGasUsed, FromRecoveredTransaction, + IntoRecoveredTransaction, Signature, Transaction, TransactionKind, TransactionSigned, + TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, TxType, EIP1559_TX_TYPE_ID, + EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, }; pub use withdrawal::Withdrawal; diff --git a/crates/primitives/src/transaction/access_list.rs b/crates/primitives/src/transaction/access_list.rs index 823a98bbc4..d7295f5dcc 100644 --- a/crates/primitives/src/transaction/access_list.rs +++ b/crates/primitives/src/transaction/access_list.rs @@ -3,6 +3,7 @@ use revm_primitives::U256; use reth_codecs::{main_codec, Compact}; use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; +use serde::{Deserialize, Serialize}; /// A list of addresses and storage keys that the transaction plans to access. /// Accesses outside the list are possible, but become more expensive. @@ -34,3 +35,13 @@ impl AccessList { .collect() } } + +/// Access list with gas used appended. +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AccessListWithGasUsed { + /// List with accounts accessed during transaction. + pub access_list: AccessList, + /// Estimated gas used with access list. + pub gas_used: U256, +} diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 12b495a763..45708a048c 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1,5 +1,5 @@ use crate::{keccak256, Address, Bytes, ChainId, TxHash, H256}; -pub use access_list::{AccessList, AccessListItem}; +pub use access_list::{AccessList, AccessListItem, AccessListWithGasUsed}; use bytes::{Buf, BytesMut}; use derive_more::{AsRef, Deref}; use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index de419c346d..ae1a639554 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -12,5 +12,6 @@ reth-primitives = { path = "../primitives" } reth-interfaces = { path = "../interfaces" } reth-provider = { path = "../storage/provider" } reth-revm-primitives = { path = "./revm-primitives" } +reth-revm-inspectors = { path = "./revm-inspectors" } -revm = { version = "3.0.0"} \ No newline at end of file +revm = { version = "3.0.0" } diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 781ae36c33..c4541a2ee8 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -10,6 +10,8 @@ /// Contains glue code for integrating reth database into revm's [Database](revm::Database). pub mod database; +/// reexport for convenience +pub use reth_revm_inspectors::*; /// reexport for convenience pub use reth_revm_primitives::*; diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-api/src/eth.rs index b16cf6bf0f..dc715088f6 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-api/src/eth.rs @@ -1,7 +1,6 @@ use jsonrpsee::{core::RpcResult as Result, proc_macros::rpc}; use reth_primitives::{ - rpc::transaction::eip2930::AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, - H256, H64, U256, U64, + AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, H256, H64, U256, U64, }; use reth_rpc_types::{ state::StateOverride, CallRequest, EIP1186AccountProofResponse, FeeHistory, Index, RichBlock, diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 10b2d44ca7..7978378148 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -75,6 +75,8 @@ where EthApiClient::block_uncles_count_by_number(client, block_number).await.unwrap(); EthApiClient::uncle_by_block_hash_and_index(client, hash, index).await.unwrap(); EthApiClient::uncle_by_block_number_and_index(client, block_number, index).await.unwrap(); + EthApiClient::create_access_list(client, call_request.clone(), None).await.unwrap(); + // Unimplemented assert!(is_unimplemented(EthApiClient::syncing(client).await.err().unwrap())); assert!(is_unimplemented(EthApiClient::author(client).await.err().unwrap())); @@ -92,9 +94,6 @@ where assert!(is_unimplemented( EthApiClient::call(client, call_request.clone(), None, None).await.err().unwrap() )); - assert!(is_unimplemented( - EthApiClient::create_access_list(client, call_request.clone(), None).await.err().unwrap() - )); assert!(is_unimplemented( EthApiClient::estimate_gas(client, call_request.clone(), None).await.err().unwrap() )); diff --git a/crates/rpc/rpc-types/src/eth/log.rs b/crates/rpc/rpc-types/src/eth/log.rs index 0eee01fbbd..06becfe555 100644 --- a/crates/rpc/rpc-types/src/eth/log.rs +++ b/crates/rpc/rpc-types/src/eth/log.rs @@ -31,7 +31,6 @@ pub struct Log { #[cfg(test)] mod tests { use super::*; - use serde_json; #[test] fn serde_log() { diff --git a/crates/rpc/rpc-types/src/eth/transaction/mod.rs b/crates/rpc/rpc-types/src/eth/transaction/mod.rs index bce7315233..a04a3cbd78 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/mod.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/mod.rs @@ -157,7 +157,6 @@ impl Transaction { #[cfg(test)] mod tests { use super::*; - use serde_json; #[test] fn serde_transaction() { diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 34e20387a1..949fb3313e 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -16,7 +16,9 @@ reth-rpc-api = { path = "../rpc-api" } reth-rlp = { path = "../../rlp" } reth-rpc-types = { path = "../rpc-types" } reth-provider = { path = "../../storage/provider", features = ["test-utils"] } -reth-transaction-pool = { path = "../../transaction-pool", features = ["test-utils"]} +reth-transaction-pool = { path = "../../transaction-pool", features = [ + "test-utils", +] } reth-network-api = { path = "../../net/network-api", features = ["test-utils"] } reth-rpc-engine-api = { path = "../rpc-engine-api" } reth-revm = { path = "../../revm" } @@ -56,5 +58,4 @@ schnellru = "0.2" futures = "0.3.26" [dev-dependencies] -jsonrpsee = { version = "0.16", features = ["client"]} - +jsonrpsee = { version = "0.16", features = ["client"] } diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index 7c8ffc7069..dc6e4146ab 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -6,26 +6,33 @@ use crate::{ eth::error::{EthApiError, EthResult, InvalidTransactionError, RevertError}, EthApi, }; +use ethers_core::utils::get_contract_address; use reth_primitives::{ - AccessList, Address, BlockId, BlockNumberOrTag, Bytes, TransactionKind, U128, U256, + AccessList, AccessListItem, AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, + TransactionKind, H256, U128, U256, }; use reth_provider::{BlockProvider, EvmEnvProvider, StateProvider, StateProviderFactory}; -use reth_revm::database::{State, SubState}; +use reth_revm::{ + access_list::AccessListInspector, + database::{State, SubState}, +}; use reth_rpc_types::{ state::{AccountOverride, StateOverride}, CallRequest, }; use revm::{ db::{CacheDB, DatabaseRef}, + precompile::{Precompiles, SpecId as PrecompilesSpecId}, primitives::{ ruint::Uint, BlockEnv, Bytecode, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, - TransactTo, TxEnv, + SpecId, TransactTo, TxEnv, }, - Database, + Database, Inspector, }; // Gas per transaction not creating a contract. const MIN_TRANSACTION_GAS: u64 = 21_000u64; +const MIN_CREATE_GAS: u64 = 53_000u64; impl EthApi where @@ -222,11 +229,14 @@ where // transaction requires to succeed let gas_used = res.result.gas_used(); // the lowest value is capped by the gas it takes for a transfer - let mut lowest_gas_limit = MIN_TRANSACTION_GAS; + let mut lowest_gas_limit = + if env.tx.transact_to.is_create() { MIN_CREATE_GAS } else { MIN_TRANSACTION_GAS }; let mut highest_gas_limit: u64 = highest_gas_limit.try_into().unwrap_or(u64::MAX); // pick a point that's close to the estimated gas - let mut mid_gas_limit = - std::cmp::min(gas_used * 3, (highest_gas_limit + lowest_gas_limit) / 2); + let mut mid_gas_limit = std::cmp::min( + gas_used * 3, + ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64, + ); let mut last_highest_gas_limit = highest_gas_limit; @@ -241,10 +251,10 @@ where highest_gas_limit = mid_gas_limit; // if last two successful estimations only vary by 10%, we consider this to be // sufficiently accurate - const ACCURACY: u64 = 10; - if (last_highest_gas_limit - highest_gas_limit) * ACCURACY / - last_highest_gas_limit < - 1u64 + const ACCURACY: u128 = 10; + if (last_highest_gas_limit - highest_gas_limit) as u128 * ACCURACY / + (last_highest_gas_limit as u128) < + 1u128 { return Ok(U256::from(highest_gas_limit)) } @@ -269,11 +279,77 @@ where } } // new midpoint - mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; + mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64; } Ok(U256::from(highest_gas_limit)) } + + pub(crate) async fn create_access_list_at( + &self, + request: CallRequest, + at: Option, + ) -> EthResult { + let block_id = at.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); + let (mut cfg, block, at) = self.evm_env_at(block_id).await?; + let state = self.state_at_block_id(at)?.ok_or_else(|| EthApiError::UnknownBlockNumber)?; + + // we want to disable this in eth_call, since this is common practice used by other node + // impls and providers + cfg.disable_block_gas_limit = true; + + let mut env = build_call_evm_env(cfg, block, request.clone())?; + let mut db = SubState::new(State::new(state)); + + let from = request.from.unwrap_or_default(); + let to = if let Some(to) = request.to { + to + } else { + let nonce = db.basic(from)?.unwrap_or_default().nonce; + get_contract_address(from, nonce).into() + }; + + let initial = request.access_list.clone().unwrap_or_default(); + + let precompiles = get_precompiles(&env.cfg.spec_id); + let mut inspector = AccessListInspector::new(initial, from, to, precompiles); + let (result, _env) = inspect(&mut db, env, &mut inspector)?; + + match result.result { + ExecutionResult::Halt { reason, .. } => Err(match reason { + Halt::NonceOverflow => InvalidTransactionError::NonceMaxValue, + halt => InvalidTransactionError::EvmHalt(halt), + }), + ExecutionResult::Revert { output, .. } => { + Err(InvalidTransactionError::Revert(RevertError::new(output))) + } + ExecutionResult::Success { .. } => Ok(()), + }?; + Ok(inspector.into_access_list()) + } +} + +/// Returns the addresses of the precompiles corresponding to the SpecId. +fn get_precompiles(spec_id: &SpecId) -> Vec { + 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. @@ -288,6 +364,19 @@ where Ok((res, evm.env)) } +/// Executes the [Env] against the given [Database] without committing state changes. +pub(crate) fn inspect(db: S, env: Env, inspector: I) -> EthResult<(ResultAndState, Env)> +where + S: Database, + ::Error: Into, + I: Inspector, +{ + 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(crate) fn build_call_evm_env( mut cfg: CfgEnv, @@ -317,7 +406,7 @@ fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthResult, + mut request: CallRequest, + block_number: Option, ) -> Result { - Err(internal_rpc_err("unimplemented")) + let block_id = block_number.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); + let access_list = self.create_access_list_at(request.clone(), block_number).await?; + request.access_list = Some(access_list.clone()); + let gas_used = self.estimate_gas_at(request, block_id).await?; + Ok(AccessListWithGasUsed { access_list, gas_used }) } /// Handler for: `eth_estimateGas` diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 19801249ef..cc843246f4 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -298,6 +298,7 @@ impl EvmEnvProvider for ShareableDatabase { impl StateProviderFactory for ShareableDatabase { type HistorySP<'a> = HistoricalStateProvider<'a,>::TX> where Self: 'a; type LatestSP<'a> = LatestStateProvider<'a,>::TX> where Self: 'a; + /// Storage provider for latest block fn latest(&self) -> Result> { Ok(LatestStateProvider::new(self.db.tx()?))