feat(rpc): add eth_createAccessList implementation (#1652)

Co-authored-by: lambdaclass-user <github@lambdaclass.com>
This commit is contained in:
Tomás
2023-03-08 06:07:09 -03:00
committed by GitHub
parent a34226c8e5
commit 77d108660e
14 changed files with 139 additions and 33 deletions

1
Cargo.lock generated
View File

@ -4868,6 +4868,7 @@ dependencies = [
"reth-interfaces",
"reth-primitives",
"reth-provider",
"reth-revm-inspectors",
"reth-revm-primitives",
"revm",
]

View File

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

View File

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

View File

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

View File

@ -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"}
revm = { version = "3.0.0" }

View File

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

View File

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

View File

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

View File

@ -31,7 +31,6 @@ pub struct Log {
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn serde_log() {

View File

@ -157,7 +157,6 @@ impl Transaction {
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn serde_transaction() {

View File

@ -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"] }

View File

@ -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<Client, Pool, Network> EthApi<Client, Pool, Network>
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<BlockId>,
) -> EthResult<AccessList> {
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 <https://github.com/foundry-rs/foundry/issues/4388>
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<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.
@ -288,6 +364,19 @@ where
Ok((res, evm.env))
}
/// Executes the [Env] against the given [Database] without committing state changes.
pub(crate) fn inspect<S, I>(db: S, env: Env, inspector: I) -> EthResult<(ResultAndState, Env)>
where
S: Database,
<S as Database>::Error: Into<EthApiError>,
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(crate) fn build_call_evm_env(
mut cfg: CfgEnv,
@ -317,7 +406,7 @@ fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthResult<TxEnv
let CallFees { max_priority_fee_per_gas, gas_price } =
CallFees::ensure_fees(gas_price, max_fee_per_gas, max_priority_fee_per_gas)?;
let gas_limit = gas.unwrap_or(block_env.gas_limit);
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)?,

View File

@ -8,8 +8,7 @@ use crate::{
};
use jsonrpsee::core::RpcResult as Result;
use reth_primitives::{
rpc::transaction::eip2930::AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes,
Header, H256, H64, U256, U64,
AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, Header, H256, H64, U256, U64,
};
use reth_provider::{BlockProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory};
use reth_rpc_api::EthApiServer;
@ -190,10 +189,14 @@ where
/// Handler for: `eth_createAccessList`
async fn create_access_list(
&self,
_request: CallRequest,
_block_number: Option<BlockId>,
mut request: CallRequest,
block_number: Option<BlockId>,
) -> Result<AccessListWithGasUsed> {
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`

View File

@ -298,6 +298,7 @@ impl<DB: Database> EvmEnvProvider for ShareableDatabase<DB> {
impl<DB: Database> StateProviderFactory for ShareableDatabase<DB> {
type HistorySP<'a> = HistoricalStateProvider<'a,<DB as DatabaseGAT<'a>>::TX> where Self: 'a;
type LatestSP<'a> = LatestStateProvider<'a,<DB as DatabaseGAT<'a>>::TX> where Self: 'a;
/// Storage provider for latest block
fn latest(&self) -> Result<Self::LatestSP<'_>> {
Ok(LatestStateProvider::new(self.db.tx()?))