mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(rpc): add eth_createAccessList implementation (#1652)
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4868,6 +4868,7 @@ dependencies = [
|
||||
"reth-interfaces",
|
||||
"reth-primitives",
|
||||
"reth-provider",
|
||||
"reth-revm-inspectors",
|
||||
"reth-revm-primitives",
|
||||
"revm",
|
||||
]
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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::*;
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
));
|
||||
|
||||
@ -31,7 +31,6 @@ pub struct Log {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json;
|
||||
|
||||
#[test]
|
||||
fn serde_log() {
|
||||
|
||||
@ -157,7 +157,6 @@ impl Transaction {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json;
|
||||
|
||||
#[test]
|
||||
fn serde_transaction() {
|
||||
|
||||
@ -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"] }
|
||||
|
||||
@ -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)?,
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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()?))
|
||||
|
||||
Reference in New Issue
Block a user