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, Bytes};
use alloy_provider::Provider;
use crate::cache::{AccountProof, AccountProofFetchFn, account_proof_fetcher};
use crate::errors::StorageFetchResult;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct AccountCodeClaim {
address: Address,
expected_code_hash: B256,
}
impl AccountCodeClaim {
pub const fn new(address: Address, expected_code_hash: B256) -> Self {
Self {
address,
expected_code_hash,
}
}
pub const fn address(self) -> Address {
self.address
}
pub const fn expected_code_hash(self) -> B256 {
self.expected_code_hash
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AccountProofRoundRequest {
block_hash: B256,
claims: Vec<AccountCodeClaim>,
}
impl AccountProofRoundRequest {
pub fn new(block_hash: B256, claims: impl IntoIterator<Item = AccountCodeClaim>) -> Self {
Self {
block_hash,
claims: claims.into_iter().collect(),
}
}
pub const fn block_hash(&self) -> B256 {
self.block_hash
}
pub fn claims(&self) -> &[AccountCodeClaim] {
&self.claims
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AccountProofOutcome {
Verified {
address: Address,
proof: AccountProof,
},
Mismatch {
address: Address,
expected: B256,
actual: B256,
proof: AccountProof,
},
FetchFailed {
address: Address,
reason: String,
},
}
impl AccountProofOutcome {
pub const fn address(&self) -> Address {
match self {
Self::Verified { address, .. }
| Self::Mismatch { address, .. }
| Self::FetchFailed { address, .. } => *address,
}
}
pub const fn verified_proof(&self) -> Option<&AccountProof> {
match self {
Self::Verified { proof, .. } => Some(proof),
Self::Mismatch { .. } | Self::FetchFailed { .. } => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AccountProofRoundFetch {
block_hash: B256,
outcomes: Vec<AccountProofOutcome>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreparedAccountValue {
address: Address,
proof: AccountProof,
code: Bytes,
}
impl PreparedAccountValue {
pub const fn new(address: Address, proof: AccountProof, code: Bytes) -> Self {
Self {
address,
proof,
code,
}
}
pub const fn address(&self) -> Address {
self.address
}
pub const fn proof(&self) -> &AccountProof {
&self.proof
}
pub const fn code(&self) -> &Bytes {
&self.code
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PreparedAccountPatch {
block_hash: B256,
verified_at_block: u64,
values: Vec<PreparedAccountValue>,
}
impl PreparedAccountPatch {
pub fn new(
block_hash: B256,
verified_at_block: u64,
values: impl IntoIterator<Item = PreparedAccountValue>,
) -> Self {
Self {
block_hash,
verified_at_block,
values: values.into_iter().collect(),
}
}
pub const fn block_hash(&self) -> B256 {
self.block_hash
}
pub const fn verified_at_block(&self) -> u64 {
self.verified_at_block
}
pub fn values(&self) -> &[PreparedAccountValue] {
&self.values
}
}
impl AccountProofRoundFetch {
pub const fn block_hash(&self) -> B256 {
self.block_hash
}
pub fn outcomes(&self) -> &[AccountProofOutcome] {
&self.outcomes
}
pub fn into_outcomes(self) -> Vec<AccountProofOutcome> {
self.outcomes
}
}
#[derive(Clone)]
pub struct AccountProofRoundFetcher {
fetcher: AccountProofFetchFn,
}
impl fmt::Debug for AccountProofRoundFetcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AccountProofRoundFetcher")
.finish_non_exhaustive()
}
}
impl AccountProofRoundFetcher {
pub fn new(fetcher: AccountProofFetchFn) -> Self {
Self { fetcher }
}
pub fn from_provider<P>(provider: Arc<P>, max_concurrent_proofs: usize) -> Self
where
P: Provider<AnyNetwork> + 'static,
{
Self::new(account_proof_fetcher(provider, max_concurrent_proofs))
}
pub fn fetch(
&self,
request: &AccountProofRoundRequest,
) -> Result<AccountProofRoundFetch, AccountProofRoundFetchError> {
let mut requested = HashSet::with_capacity(request.claims.len());
for claim in &request.claims {
if !requested.insert(claim.address) {
return Err(AccountProofRoundFetchError::DuplicateRequest {
address: claim.address,
});
}
}
let provider_requests = request
.claims
.iter()
.map(|claim| (claim.address, Vec::new()))
.collect();
let response = (self.fetcher)(
provider_requests,
BlockId::from((request.block_hash, Some(true))),
);
let mut returned: HashMap<Address, StorageFetchResult<AccountProof>> =
HashMap::with_capacity(response.len());
for (address, proof) in response {
if !requested.contains(&address) {
return Err(AccountProofRoundFetchError::UnexpectedResult { address });
}
if returned.insert(address, proof).is_some() {
return Err(AccountProofRoundFetchError::DuplicateResult { address });
}
}
for address in requested {
if !returned.contains_key(&address) {
return Err(AccountProofRoundFetchError::MissingResult { address });
}
}
let outcomes = request
.claims
.iter()
.map(|claim| {
match returned
.remove(&claim.address)
.expect("provider response completeness validated above")
{
Ok(proof) if proof.code_hash == claim.expected_code_hash => {
AccountProofOutcome::Verified {
address: claim.address,
proof,
}
}
Ok(proof) => AccountProofOutcome::Mismatch {
address: claim.address,
expected: claim.expected_code_hash,
actual: proof.code_hash,
proof,
},
Err(error) => AccountProofOutcome::FetchFailed {
address: claim.address,
reason: error.to_string(),
},
}
})
.collect();
Ok(AccountProofRoundFetch {
block_hash: request.block_hash,
outcomes,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum AccountProofRoundFetchError {
#[error("duplicate account-proof request for {address}")]
DuplicateRequest {
address: Address,
},
#[error("account proof provider returned unexpected account {address}")]
UnexpectedResult {
address: Address,
},
#[error("account proof provider returned duplicate account {address}")]
DuplicateResult {
address: Address,
},
#[error("account proof provider omitted requested account {address}")]
MissingResult {
address: Address,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum PreparedAccountPatchError {
#[error("prepared account patch baseline mismatch: prepared {prepared}, cache {cache:?}")]
BaselineMismatch {
prepared: B256,
cache: Option<B256>,
},
#[error("prepared account patch contains duplicate account {address}")]
DuplicateAccount {
address: Address,
},
#[error("prepared account patch contains empty runtime code for {address}")]
EmptyCode {
address: Address,
},
#[error("prepared account proof for {address} unexpectedly contains {slots} storage slots")]
UnexpectedProofSlots {
address: Address,
slots: usize,
},
#[error("prepared runtime code hash mismatch for {address}: proof {expected}, bytes {actual}")]
CodeHashMismatch {
address: Address,
expected: B256,
actual: B256,
},
#[error("prepared canonical account patch cannot overwrite etched account {address}")]
EtchedAccount {
address: Address,
},
#[error(
"prepared account seed conflict for {address}: existing {existing}, prepared {prepared}"
)]
SeedConflict {
address: Address,
existing: B256,
prepared: B256,
},
}