feat: replace reth native AccessList type (#7636)

This commit is contained in:
Matthias Seitz
2024-04-14 20:07:41 +02:00
committed by GitHub
parent d4ea41528a
commit 3e8d5c69cf
3 changed files with 62 additions and 190 deletions

View File

@ -1,202 +1,74 @@
use crate::{Address, B256, U256};
use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
use reth_codecs::{main_codec, Compact};
use std::{
mem,
ops::{Deref, DerefMut},
};
//! [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930): Access List types
/// Represents a list of addresses and storage keys that a transaction plans to access.
///
/// Accesses outside this list incur higher costs due to gas charging.
///
/// This structure is part of [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930), introducing an optional access list for Ethereum transactions.
///
/// The access list allows pre-specifying and pre-paying for accounts and storage
/// slots, mitigating risks introduced by [EIP-2929](https://eips.ethereum.org/EIPS/e).
#[main_codec(rlp)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable)]
#[serde(rename_all = "camelCase")]
pub struct AccessListItem {
/// Account address that would be loaded at the start of execution
pub address: Address,
/// The storage keys to be loaded at the start of execution.
///
/// Each key is a 32-byte value representing a specific storage slot.
#[cfg_attr(
any(test, feature = "arbitrary"),
proptest(
strategy = "proptest::collection::vec(proptest::arbitrary::any::<B256>(), 0..=20)"
)
)]
pub storage_keys: Vec<B256>,
}
impl AccessListItem {
/// Calculates a heuristic for the in-memory size of the [AccessListItem].
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<Address>() + self.storage_keys.capacity() * mem::size_of::<B256>()
}
}
/// AccessList as defined in [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)
#[main_codec(rlp)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper)]
pub struct AccessList(
#[cfg_attr(
any(test, feature = "arbitrary"),
proptest(
strategy = "proptest::collection::vec(proptest::arbitrary::any::<AccessListItem>(), 0..=20)"
)
)]
pub Vec<AccessListItem>,
);
impl AccessList {
/// Converts the list into a vec, expected by revm
pub fn flattened(&self) -> Vec<(Address, Vec<U256>)> {
self.flatten().collect()
}
/// Consumes the type and converts the list into a vec, expected by revm
pub fn into_flattened(self) -> Vec<(Address, Vec<U256>)> {
self.into_flatten().collect()
}
/// Consumes the type and returns an iterator over the list's addresses and storage keys.
pub fn into_flatten(self) -> impl Iterator<Item = (Address, Vec<U256>)> {
self.0.into_iter().map(|item| {
(
item.address,
item.storage_keys.into_iter().map(|slot| U256::from_be_bytes(slot.0)).collect(),
)
})
}
/// Returns an iterator over the list's addresses and storage keys.
pub fn flatten(&self) -> impl Iterator<Item = (Address, Vec<U256>)> + '_ {
self.iter().map(|item| {
(
item.address,
item.storage_keys.iter().map(|slot| U256::from_be_bytes(slot.0)).collect(),
)
})
}
/// Calculates a heuristic for the in-memory size of the [AccessList].
#[inline]
pub fn size(&self) -> usize {
// take into account capacity
self.iter().map(AccessListItem::size).sum::<usize>() +
self.capacity() * mem::size_of::<AccessListItem>()
}
/// Returns the position of the given address in the access list, if present.
pub fn index_of_address(&self, address: Address) -> Option<usize> {
self.iter().position(|item| item.address == address)
}
/// Checks if a specific storage slot within an account is present in the access list.
///
/// Returns a tuple with flags for the presence of the account and the slot.
pub fn contains(&self, address: Address, slot: B256) -> (bool, bool) {
self.index_of_address(address)
.map_or((false, false), |idx| (true, self.contains_storage_key_at_index(slot, idx)))
}
/// Checks if the access list contains the specified address.
pub fn contains_address(&self, address: Address) -> bool {
self.iter().any(|item| item.address == address)
}
/// Checks if the storage keys at the given index within an account are present in the access
/// list.
pub fn contains_storage_key_at_index(&self, slot: B256, index: usize) -> bool {
self.get(index).map_or(false, |entry| {
entry.storage_keys.iter().any(|storage_key| *storage_key == slot)
})
}
/// Adds an address to the access list and returns `true` if the operation results in a change,
/// indicating that the address was not previously present.
pub fn add_address(&mut self, address: Address) -> bool {
!self.contains_address(address) && {
self.push(AccessListItem { address, storage_keys: Vec::new() });
true
}
}
}
impl Deref for AccessList {
type Target = Vec<AccessListItem>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for AccessList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<reth_rpc_types::AccessList> for AccessList {
#[inline]
fn from(value: reth_rpc_types::AccessList) -> Self {
AccessList(
value
.0
.into_iter()
.map(|item| AccessListItem {
address: item.address,
storage_keys: item.storage_keys,
})
.collect(),
)
}
}
impl From<AccessList> for reth_rpc_types::AccessList {
#[inline]
fn from(value: AccessList) -> Self {
reth_rpc_types::AccessList(
value
.0
.into_iter()
.map(|item| reth_rpc_types::AccessListItem {
address: item.address,
storage_keys: item.storage_keys,
})
.collect(),
)
}
}
/// Re-export from `alloy_eips`.
#[doc(inline)]
pub use alloy_eips::eip2930::{AccessList, AccessListItem};
#[cfg(test)]
mod tests {
use super::*;
use crate::{Address, B256};
use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
use proptest::proptest;
use reth_codecs::{main_codec, Compact};
/// This type is kept for compatibility tests after the codec support was added to alloy-eips
/// AccessList type natively
#[main_codec(rlp)]
#[derive(
Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper,
)]
struct RethAccessList(
#[proptest(
strategy = "proptest::collection::vec(proptest::arbitrary::any::<RethAccessListItem>(), 0..=20)"
)]
Vec<RethAccessListItem>,
);
impl PartialEq<AccessList> for RethAccessList {
fn eq(&self, other: &AccessList) -> bool {
self.0.iter().zip(other.iter()).all(|(a, b)| a == b)
}
}
// This
#[main_codec(rlp)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable)]
#[serde(rename_all = "camelCase")]
struct RethAccessListItem {
/// Account address that would be loaded at the start of execution
address: Address,
/// The storage keys to be loaded at the start of execution.
///
/// Each key is a 32-byte value representing a specific storage slot.
#[proptest(
strategy = "proptest::collection::vec(proptest::arbitrary::any::<B256>(), 0..=20)"
)]
storage_keys: Vec<B256>,
}
impl PartialEq<AccessListItem> for RethAccessListItem {
fn eq(&self, other: &AccessListItem) -> bool {
self.address == other.address && self.storage_keys == other.storage_keys
}
}
proptest!(
#[test]
fn test_roundtrip_accesslist_conversion(access_list: AccessList) {
fn test_roundtrip_accesslist_compat(access_list: RethAccessList) {
// Convert access_list to buffer and then create alloy_access_list from buffer and
// compare
let mut compacted_access_list = Vec::<u8>::new();
let len = access_list.clone().to_compact(&mut compacted_access_list);
let mut compacted_reth_access_list = Vec::<u8>::new();
let len = access_list.clone().to_compact(&mut compacted_reth_access_list);
let alloy_access_list = AccessList::from_compact(&compacted_access_list, len).0;
// decode the compacted buffer to AccessList
let alloy_access_list = AccessList::from_compact(&compacted_reth_access_list, len).0;
assert_eq!(access_list, alloy_access_list);
// Create alloy_access_list from access_list and then convert it to buffer and compare
// compacted_alloy_access_list and compacted_access_list
let alloy_access_list = AccessList(access_list.0);
let mut compacted_alloy_access_list = Vec::<u8>::new();
let _len = alloy_access_list.to_compact(&mut compacted_alloy_access_list);
assert_eq!(compacted_access_list, compacted_alloy_access_list);
let alloy_len = alloy_access_list.to_compact(&mut compacted_alloy_access_list);
assert_eq!(len, alloy_len);
assert_eq!(compacted_reth_access_list, compacted_alloy_access_list);
}
);
}

View File

@ -652,7 +652,7 @@ impl TryFrom<reth_rpc_types::Transaction> for Transaction {
to: tx.to.map_or(TransactionKind::Create, TransactionKind::Call),
value: tx.value,
input: tx.input,
access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?.into(),
access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?,
gas_price: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?,
}))
}
@ -673,7 +673,7 @@ impl TryFrom<reth_rpc_types::Transaction> for Transaction {
.map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?,
to: tx.to.map_or(TransactionKind::Create, TransactionKind::Call),
value: tx.value,
access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?.into(),
access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?,
input: tx.input,
}))
}
@ -694,7 +694,7 @@ impl TryFrom<reth_rpc_types::Transaction> for Transaction {
.map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?,
to: tx.to.map_or(TransactionKind::Create, TransactionKind::Call),
value: tx.value,
access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?.into(),
access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?,
input: tx.input,
blob_versioned_hashes: tx
.blob_versioned_hashes

View File

@ -28,7 +28,7 @@ pub fn to_primitive_transaction(
to: to_primitive_transaction_kind(tx.kind),
value: tx.value,
input: tx.input,
access_list: tx.access_list.into(),
access_list: tx.access_list,
}),
TypedTransactionRequest::EIP1559(tx) => Transaction::Eip1559(TxEip1559 {
chain_id: tx.chain_id,
@ -38,7 +38,7 @@ pub fn to_primitive_transaction(
to: to_primitive_transaction_kind(tx.kind),
value: tx.value,
input: tx.input,
access_list: tx.access_list.into(),
access_list: tx.access_list,
max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(),
}),
TypedTransactionRequest::EIP4844(tx) => Transaction::Eip4844(TxEip4844 {
@ -49,7 +49,7 @@ pub fn to_primitive_transaction(
max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(),
to: to_primitive_transaction_kind(tx.kind),
value: tx.value,
access_list: tx.access_list.into(),
access_list: tx.access_list,
blob_versioned_hashes: tx.blob_versioned_hashes,
max_fee_per_blob_gas: tx.max_fee_per_blob_gas.to(),
input: tx.input,