use std::collections::{HashMap, HashSet};
use std::fmt;
use std::sync::Arc;
use alloy_eips::BlockId;
use alloy_network::AnyNetwork;
use alloy_primitives::{Address, B256, U256};
use alloy_provider::Provider;
use crate::cache::{
EvmCache, StorageBatchConfig, StorageBatchFetchFn, StorageFetchStrategy,
provider_storage_fetcher,
};
use crate::errors::StorageFetchResult;
use crate::freshness::{SlotFetch, SlotOutcome};
use crate::state_update::{StateDiff, StateUpdate};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StorageSlotRequest {
address: Address,
slot: U256,
}
impl StorageSlotRequest {
pub const fn new(address: Address, slot: U256) -> Self {
Self { address, slot }
}
pub const fn address(self) -> Address {
self.address
}
pub const fn slot(self) -> U256 {
self.slot
}
}
impl From<(Address, U256)> for StorageSlotRequest {
fn from((address, slot): (Address, U256)) -> Self {
Self::new(address, slot)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PreparedStorageValue {
address: Address,
slot: U256,
value: U256,
}
impl PreparedStorageValue {
pub const fn new(address: Address, slot: U256, value: U256) -> Self {
Self {
address,
slot,
value,
}
}
pub const fn address(self) -> Address {
self.address
}
pub const fn slot(self) -> U256 {
self.slot
}
pub const fn value(self) -> U256 {
self.value
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreparedStoragePatch {
block_hash: B256,
values: Vec<PreparedStorageValue>,
}
impl EvmCache {
pub fn apply_prepared_storage_patch(
&mut self,
patch: &PreparedStoragePatch,
) -> Result<StateDiff, PreparedStoragePatchError> {
let mut identities = HashSet::with_capacity(patch.values.len());
for value in &patch.values {
if !identities.insert((value.address, value.slot)) {
return Err(PreparedStoragePatchError::DuplicateSlot {
address: value.address,
slot: value.slot,
});
}
}
let cache_hash = match self.block() {
BlockId::Hash(hash) => Some(hash.block_hash),
BlockId::Number(_) => None,
};
if cache_hash != Some(patch.block_hash) {
return Err(PreparedStoragePatchError::BaselineMismatch {
prepared: patch.block_hash,
cache: cache_hash,
});
}
let updates: Vec<_> = patch
.values
.iter()
.map(|value| StateUpdate::slot(value.address, value.slot, value.value))
.collect();
Ok(self.apply_updates(&updates))
}
}
impl PreparedStoragePatch {
pub fn new(block_hash: B256, values: impl IntoIterator<Item = PreparedStorageValue>) -> Self {
Self {
block_hash,
values: values.into_iter().collect(),
}
}
pub const fn block_hash(&self) -> B256 {
self.block_hash
}
pub fn values(&self) -> &[PreparedStorageValue] {
&self.values
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StorageRoundRequest {
block_hash: B256,
verify: Vec<StorageSlotRequest>,
probe: Vec<StorageSlotRequest>,
}
impl StorageRoundRequest {
pub fn new<V, P, VI, PI>(block_hash: B256, verify: V, probe: P) -> Self
where
V: IntoIterator<Item = VI>,
P: IntoIterator<Item = PI>,
VI: Into<StorageSlotRequest>,
PI: Into<StorageSlotRequest>,
{
Self {
block_hash,
verify: verify.into_iter().map(Into::into).collect(),
probe: probe.into_iter().map(Into::into).collect(),
}
}
pub const fn block_hash(&self) -> B256 {
self.block_hash
}
pub fn verify(&self) -> &[StorageSlotRequest] {
&self.verify
}
pub fn probe(&self) -> &[StorageSlotRequest] {
&self.probe
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StorageRoundFetch {
block_hash: B256,
verified: Vec<SlotOutcome>,
probed: Vec<SlotOutcome>,
patch: PreparedStoragePatch,
}
impl StorageRoundFetch {
pub const fn block_hash(&self) -> B256 {
self.block_hash
}
pub fn verified(&self) -> &[SlotOutcome] {
&self.verified
}
pub fn probed(&self) -> &[SlotOutcome] {
&self.probed
}
pub const fn patch(&self) -> &PreparedStoragePatch {
&self.patch
}
pub fn into_patch(self) -> PreparedStoragePatch {
self.patch
}
pub fn into_parts(self) -> (Vec<SlotOutcome>, Vec<SlotOutcome>, PreparedStoragePatch) {
(self.verified, self.probed, self.patch)
}
}
#[derive(Clone)]
pub struct StorageRoundFetcher {
fetcher: StorageBatchFetchFn,
}
impl fmt::Debug for StorageRoundFetcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StorageRoundFetcher")
.finish_non_exhaustive()
}
}
impl StorageRoundFetcher {
pub fn new(fetcher: StorageBatchFetchFn) -> Self {
Self { fetcher }
}
pub fn from_provider<P>(
provider: Arc<P>,
batch_config: StorageBatchConfig,
strategy: StorageFetchStrategy,
) -> Self
where
P: Provider<AnyNetwork> + 'static,
{
Self::new(provider_storage_fetcher(provider, batch_config, strategy))
}
pub fn fetch(
&self,
request: &StorageRoundRequest,
) -> Result<StorageRoundFetch, StorageRoundFetchError> {
let mut requested = HashSet::with_capacity(request.verify.len() + request.probe.len());
for slot in request.verify.iter().chain(&request.probe) {
if !requested.insert((slot.address, slot.slot)) {
return Err(StorageRoundFetchError::DuplicateRequest {
address: slot.address,
slot: slot.slot,
});
}
}
let provider_requests: Vec<_> = request
.verify
.iter()
.chain(&request.probe)
.map(|slot| (slot.address, slot.slot))
.collect();
let response = (self.fetcher)(
provider_requests,
BlockId::from((request.block_hash, Some(true))),
);
let mut returned: HashMap<(Address, U256), StorageFetchResult<U256>> =
HashMap::with_capacity(response.len());
for (address, slot, value) in response {
if !requested.contains(&(address, slot)) {
return Err(StorageRoundFetchError::UnexpectedResult { address, slot });
}
if returned.insert((address, slot), value).is_some() {
return Err(StorageRoundFetchError::DuplicateResult { address, slot });
}
}
for &(address, slot) in &requested {
if !returned.contains_key(&(address, slot)) {
return Err(StorageRoundFetchError::MissingResult { address, slot });
}
}
let mut patch = Vec::with_capacity(request.verify.len());
let verified = take_outcomes(&request.verify, &mut returned, Some(&mut patch));
let probed = take_outcomes(&request.probe, &mut returned, None);
Ok(StorageRoundFetch {
block_hash: request.block_hash,
verified,
probed,
patch: PreparedStoragePatch::new(request.block_hash, patch),
})
}
}
fn take_outcomes(
requests: &[StorageSlotRequest],
returned: &mut HashMap<(Address, U256), StorageFetchResult<U256>>,
mut patch: Option<&mut Vec<PreparedStorageValue>>,
) -> Vec<SlotOutcome> {
requests
.iter()
.map(|request| {
let fetched = returned
.remove(&(request.address, request.slot))
.expect("provider response completeness validated above");
let fetch = match fetched {
Ok(value) => {
if let Some(values) = patch.as_deref_mut() {
values.push(PreparedStorageValue::new(
request.address,
request.slot,
value,
));
}
if value == U256::ZERO {
SlotFetch::Zero
} else {
SlotFetch::Value(value)
}
}
Err(error) => SlotFetch::FetchFailed {
reason: error.to_string(),
},
};
SlotOutcome {
address: request.address,
slot: request.slot,
fetch,
}
})
.collect()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum StorageRoundFetchError {
#[error("duplicate storage-round request for ({address}, {slot})")]
DuplicateRequest {
address: Address,
slot: U256,
},
#[error("storage provider returned unexpected slot ({address}, {slot})")]
UnexpectedResult {
address: Address,
slot: U256,
},
#[error("storage provider returned duplicate slot ({address}, {slot})")]
DuplicateResult {
address: Address,
slot: U256,
},
#[error("storage provider omitted requested slot ({address}, {slot})")]
MissingResult {
address: Address,
slot: U256,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum PreparedStoragePatchError {
#[error("prepared storage patch contains duplicate slot ({address}, {slot})")]
DuplicateSlot {
address: Address,
slot: U256,
},
#[error("prepared storage patch baseline mismatch: prepared {prepared}, cache {cache:?}")]
BaselineMismatch {
prepared: B256,
cache: Option<B256>,
},
}