evm_fork_cache/cold_start/
account_round.rs1use std::collections::{HashMap, HashSet};
4use std::fmt;
5use std::sync::Arc;
6
7use alloy_eips::BlockId;
8use alloy_network::AnyNetwork;
9use alloy_primitives::{Address, B256, Bytes};
10use alloy_provider::Provider;
11
12use crate::cache::{AccountProof, AccountProofFetchFn, account_proof_fetcher};
13use crate::errors::StorageFetchResult;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
17pub struct AccountCodeClaim {
18 address: Address,
19 expected_code_hash: B256,
20}
21
22impl AccountCodeClaim {
23 pub const fn new(address: Address, expected_code_hash: B256) -> Self {
26 Self {
27 address,
28 expected_code_hash,
29 }
30 }
31
32 pub const fn address(self) -> Address {
34 self.address
35 }
36
37 pub const fn expected_code_hash(self) -> B256 {
39 self.expected_code_hash
40 }
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct AccountProofRoundRequest {
46 block_hash: B256,
47 claims: Vec<AccountCodeClaim>,
48}
49
50impl AccountProofRoundRequest {
51 pub fn new(block_hash: B256, claims: impl IntoIterator<Item = AccountCodeClaim>) -> Self {
54 Self {
55 block_hash,
56 claims: claims.into_iter().collect(),
57 }
58 }
59
60 pub const fn block_hash(&self) -> B256 {
62 self.block_hash
63 }
64
65 pub fn claims(&self) -> &[AccountCodeClaim] {
67 &self.claims
68 }
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
73#[non_exhaustive]
74pub enum AccountProofOutcome {
75 Verified {
77 address: Address,
79 proof: AccountProof,
81 },
82 Mismatch {
85 address: Address,
87 expected: B256,
89 actual: B256,
91 proof: AccountProof,
93 },
94 FetchFailed {
96 address: Address,
98 reason: String,
100 },
101}
102
103impl AccountProofOutcome {
104 pub const fn address(&self) -> Address {
106 match self {
107 Self::Verified { address, .. }
108 | Self::Mismatch { address, .. }
109 | Self::FetchFailed { address, .. } => *address,
110 }
111 }
112
113 pub const fn verified_proof(&self) -> Option<&AccountProof> {
115 match self {
116 Self::Verified { proof, .. } => Some(proof),
117 Self::Mismatch { .. } | Self::FetchFailed { .. } => None,
118 }
119 }
120}
121
122#[derive(Clone, Debug, PartialEq, Eq)]
124pub struct AccountProofRoundFetch {
125 block_hash: B256,
126 outcomes: Vec<AccountProofOutcome>,
127}
128
129#[derive(Clone, Debug, PartialEq, Eq)]
131pub struct PreparedAccountValue {
132 address: Address,
133 proof: AccountProof,
134 code: Bytes,
135}
136
137impl PreparedAccountValue {
138 pub const fn new(address: Address, proof: AccountProof, code: Bytes) -> Self {
141 Self {
142 address,
143 proof,
144 code,
145 }
146 }
147
148 pub const fn address(&self) -> Address {
150 self.address
151 }
152
153 pub const fn proof(&self) -> &AccountProof {
155 &self.proof
156 }
157
158 pub const fn code(&self) -> &Bytes {
160 &self.code
161 }
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
166pub struct PreparedAccountPatch {
167 block_hash: B256,
168 verified_at_block: u64,
169 values: Vec<PreparedAccountValue>,
170}
171
172impl PreparedAccountPatch {
173 pub fn new(
175 block_hash: B256,
176 verified_at_block: u64,
177 values: impl IntoIterator<Item = PreparedAccountValue>,
178 ) -> Self {
179 Self {
180 block_hash,
181 verified_at_block,
182 values: values.into_iter().collect(),
183 }
184 }
185
186 pub const fn block_hash(&self) -> B256 {
188 self.block_hash
189 }
190
191 pub const fn verified_at_block(&self) -> u64 {
193 self.verified_at_block
194 }
195
196 pub fn values(&self) -> &[PreparedAccountValue] {
198 &self.values
199 }
200}
201
202impl AccountProofRoundFetch {
203 pub const fn block_hash(&self) -> B256 {
205 self.block_hash
206 }
207
208 pub fn outcomes(&self) -> &[AccountProofOutcome] {
210 &self.outcomes
211 }
212
213 pub fn into_outcomes(self) -> Vec<AccountProofOutcome> {
215 self.outcomes
216 }
217}
218
219#[derive(Clone)]
221pub struct AccountProofRoundFetcher {
222 fetcher: AccountProofFetchFn,
223}
224
225impl fmt::Debug for AccountProofRoundFetcher {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 f.debug_struct("AccountProofRoundFetcher")
228 .finish_non_exhaustive()
229 }
230}
231
232impl AccountProofRoundFetcher {
233 pub fn new(fetcher: AccountProofFetchFn) -> Self {
235 Self { fetcher }
236 }
237
238 pub fn from_provider<P>(provider: Arc<P>, max_concurrent_proofs: usize) -> Self
240 where
241 P: Provider<AnyNetwork> + 'static,
242 {
243 Self::new(account_proof_fetcher(provider, max_concurrent_proofs))
244 }
245
246 pub fn fetch(
249 &self,
250 request: &AccountProofRoundRequest,
251 ) -> Result<AccountProofRoundFetch, AccountProofRoundFetchError> {
252 let mut requested = HashSet::with_capacity(request.claims.len());
253 for claim in &request.claims {
254 if !requested.insert(claim.address) {
255 return Err(AccountProofRoundFetchError::DuplicateRequest {
256 address: claim.address,
257 });
258 }
259 }
260
261 let provider_requests = request
262 .claims
263 .iter()
264 .map(|claim| (claim.address, Vec::new()))
265 .collect();
266 let response = (self.fetcher)(
267 provider_requests,
268 BlockId::from((request.block_hash, Some(true))),
269 );
270
271 let mut returned: HashMap<Address, StorageFetchResult<AccountProof>> =
272 HashMap::with_capacity(response.len());
273 for (address, proof) in response {
274 if !requested.contains(&address) {
275 return Err(AccountProofRoundFetchError::UnexpectedResult { address });
276 }
277 if returned.insert(address, proof).is_some() {
278 return Err(AccountProofRoundFetchError::DuplicateResult { address });
279 }
280 }
281 for address in requested {
282 if !returned.contains_key(&address) {
283 return Err(AccountProofRoundFetchError::MissingResult { address });
284 }
285 }
286
287 let outcomes = request
288 .claims
289 .iter()
290 .map(|claim| {
291 match returned
292 .remove(&claim.address)
293 .expect("provider response completeness validated above")
294 {
295 Ok(proof) if proof.code_hash == claim.expected_code_hash => {
296 AccountProofOutcome::Verified {
297 address: claim.address,
298 proof,
299 }
300 }
301 Ok(proof) => AccountProofOutcome::Mismatch {
302 address: claim.address,
303 expected: claim.expected_code_hash,
304 actual: proof.code_hash,
305 proof,
306 },
307 Err(error) => AccountProofOutcome::FetchFailed {
308 address: claim.address,
309 reason: error.to_string(),
310 },
311 }
312 })
313 .collect();
314 Ok(AccountProofRoundFetch {
315 block_hash: request.block_hash,
316 outcomes,
317 })
318 }
319}
320
321#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
323#[non_exhaustive]
324pub enum AccountProofRoundFetchError {
325 #[error("duplicate account-proof request for {address}")]
327 DuplicateRequest {
328 address: Address,
330 },
331 #[error("account proof provider returned unexpected account {address}")]
333 UnexpectedResult {
334 address: Address,
336 },
337 #[error("account proof provider returned duplicate account {address}")]
339 DuplicateResult {
340 address: Address,
342 },
343 #[error("account proof provider omitted requested account {address}")]
345 MissingResult {
346 address: Address,
348 },
349}
350
351#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
353#[non_exhaustive]
354pub enum PreparedAccountPatchError {
355 #[error("prepared account patch baseline mismatch: prepared {prepared}, cache {cache:?}")]
357 BaselineMismatch {
358 prepared: B256,
360 cache: Option<B256>,
362 },
363 #[error("prepared account patch contains duplicate account {address}")]
365 DuplicateAccount {
366 address: Address,
368 },
369 #[error("prepared account patch contains empty runtime code for {address}")]
371 EmptyCode {
372 address: Address,
374 },
375 #[error("prepared account proof for {address} unexpectedly contains {slots} storage slots")]
377 UnexpectedProofSlots {
378 address: Address,
380 slots: usize,
382 },
383 #[error("prepared runtime code hash mismatch for {address}: proof {expected}, bytes {actual}")]
385 CodeHashMismatch {
386 address: Address,
388 expected: B256,
390 actual: B256,
392 },
393 #[error("prepared canonical account patch cannot overwrite etched account {address}")]
395 EtchedAccount {
396 address: Address,
398 },
399 #[error(
401 "prepared account seed conflict for {address}: existing {existing}, prepared {prepared}"
402 )]
403 SeedConflict {
404 address: Address,
406 existing: B256,
408 prepared: B256,
410 },
411}