1use crate::{AccountChanges, BlockAccessIndex};
4use alloy_primitives::{Address, U256};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub enum BlockAccessListChangeKind {
9 Storage,
11 Balance,
13 Nonce,
15 Code,
17}
18
19impl core::fmt::Display for BlockAccessListChangeKind {
20 #[inline]
21 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22 f.write_str(match self {
23 Self::Storage => "storage",
24 Self::Balance => "balance",
25 Self::Nonce => "nonce",
26 Self::Code => "code",
27 })
28 }
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, thiserror::Error)]
33pub enum BlockAccessListValidationError {
34 #[error("account {address} occurs more than once in the block access list")]
36 DuplicateAccount {
37 address: Address,
39 },
40 #[error(
42 "block access list account {address} appears after {previous}, violating canonical order"
43 )]
44 AccountsOutOfOrder {
45 previous: Address,
47 address: Address,
49 },
50 #[error("storage slot {slot:#x} occurs more than once for account {address}")]
52 DuplicateStorageKey {
53 address: Address,
55 slot: U256,
57 },
58 #[error(
60 "storage slot {slot:#x} appears after {previous:#x} for account {address}, violating canonical order"
61 )]
62 StorageKeysOutOfOrder {
63 address: Address,
65 previous: U256,
67 slot: U256,
69 },
70 #[error("storage slot {slot:#x} has an empty change list for account {address}")]
72 EmptyStorageChanges {
73 address: Address,
75 slot: U256,
77 },
78 #[error(
80 "block access index {index} occurs more than once in a {kind} change list for account {address}"
81 )]
82 DuplicateBlockAccessIndex {
83 address: Address,
85 kind: BlockAccessListChangeKind,
87 index: BlockAccessIndex,
89 },
90 #[error(
92 "block access index {index} appears after {previous} in a {kind} change list for account {address}"
93 )]
94 ChangeIndicesOutOfOrder {
95 address: Address,
97 kind: BlockAccessListChangeKind,
99 previous: BlockAccessIndex,
101 index: BlockAccessIndex,
103 },
104 #[error(
106 "block access index {index} in a {kind} change list for account {address} exceeds uint32"
107 )]
108 BlockAccessIndexOutOfRange {
109 address: Address,
111 kind: BlockAccessListChangeKind,
113 index: BlockAccessIndex,
115 },
116 #[error(
118 "block access index {index} in a {kind} change list for account {address} exceeds the block maximum {max}"
119 )]
120 BlockAccessIndexExceedsBlock {
121 address: Address,
123 kind: BlockAccessListChangeKind,
125 index: BlockAccessIndex,
127 max: BlockAccessIndex,
129 },
130}
131
132pub fn validate_block_access_list(
139 block_access_list: &[AccountChanges],
140 transaction_count: usize,
141) -> Result<(), BlockAccessListValidationError> {
142 let max_block_access_index = BlockAccessIndex::new(
143 u64::try_from(transaction_count).unwrap_or(u64::MAX).saturating_add(1),
144 );
145 let mut previous_address = None;
146
147 for account in block_access_list {
148 validate_account_order(previous_address, account.address)?;
149 previous_address = Some(account.address);
150 validate_account(account, max_block_access_index)?;
151 }
152
153 Ok(())
154}
155
156fn validate_account(
157 account: &AccountChanges,
158 max_block_access_index: BlockAccessIndex,
159) -> Result<(), BlockAccessListValidationError> {
160 let address = account.address;
161 let mut previous_slot = None;
162 for slot_changes in &account.storage_changes {
163 validate_storage_key_order(address, &mut previous_slot, slot_changes.slot)?;
164 if slot_changes.changes.is_empty() {
165 return Err(BlockAccessListValidationError::EmptyStorageChanges {
166 address,
167 slot: slot_changes.slot,
168 });
169 }
170 validate_change_indices(
171 address,
172 BlockAccessListChangeKind::Storage,
173 slot_changes.changes.iter().map(|change| change.block_access_index),
174 max_block_access_index,
175 )?;
176 }
177
178 previous_slot = None;
179 for &slot in &account.storage_reads {
180 validate_storage_key_order(address, &mut previous_slot, slot)?;
181 }
182 validate_storage_disjointness(account)?;
183
184 validate_change_indices(
185 address,
186 BlockAccessListChangeKind::Balance,
187 account.balance_changes.iter().map(|change| change.block_access_index),
188 max_block_access_index,
189 )?;
190 validate_change_indices(
191 address,
192 BlockAccessListChangeKind::Nonce,
193 account.nonce_changes.iter().map(|change| change.block_access_index),
194 max_block_access_index,
195 )?;
196 validate_change_indices(
197 address,
198 BlockAccessListChangeKind::Code,
199 account.code_changes.iter().map(|change| change.block_access_index),
200 max_block_access_index,
201 )
202}
203
204fn validate_account_order(
205 previous: Option<Address>,
206 address: Address,
207) -> Result<(), BlockAccessListValidationError> {
208 if let Some(previous) = previous {
209 if previous == address {
210 return Err(BlockAccessListValidationError::DuplicateAccount { address });
211 }
212 if previous > address {
213 return Err(BlockAccessListValidationError::AccountsOutOfOrder { previous, address });
214 }
215 }
216 Ok(())
217}
218
219fn validate_storage_key_order(
220 address: Address,
221 previous: &mut Option<U256>,
222 slot: U256,
223) -> Result<(), BlockAccessListValidationError> {
224 if let Some(previous) = *previous {
225 if previous == slot {
226 return Err(BlockAccessListValidationError::DuplicateStorageKey { address, slot });
227 }
228 if previous > slot {
229 return Err(BlockAccessListValidationError::StorageKeysOutOfOrder {
230 address,
231 previous,
232 slot,
233 });
234 }
235 }
236 *previous = Some(slot);
237 Ok(())
238}
239
240fn validate_storage_disjointness(
241 account: &AccountChanges,
242) -> Result<(), BlockAccessListValidationError> {
243 let mut changes = account.storage_changes.iter().peekable();
244 let mut reads = account.storage_reads.iter().peekable();
245
246 while let (Some(change), Some(read)) = (changes.peek(), reads.peek()) {
247 match change.slot.cmp(read) {
248 core::cmp::Ordering::Less => {
249 changes.next();
250 }
251 core::cmp::Ordering::Greater => {
252 reads.next();
253 }
254 core::cmp::Ordering::Equal => {
255 return Err(BlockAccessListValidationError::DuplicateStorageKey {
256 address: account.address,
257 slot: **read,
258 });
259 }
260 }
261 }
262
263 Ok(())
264}
265
266fn validate_change_indices(
267 address: Address,
268 kind: BlockAccessListChangeKind,
269 indices: impl IntoIterator<Item = BlockAccessIndex>,
270 max: BlockAccessIndex,
271) -> Result<(), BlockAccessListValidationError> {
272 let mut previous = None;
273 for index in indices {
274 if index.get() > u32::MAX as u64 {
275 return Err(BlockAccessListValidationError::BlockAccessIndexOutOfRange {
276 address,
277 kind,
278 index,
279 });
280 }
281 if index > max {
282 return Err(BlockAccessListValidationError::BlockAccessIndexExceedsBlock {
283 address,
284 kind,
285 index,
286 max,
287 });
288 }
289 if let Some(previous) = previous {
290 if previous == index {
291 return Err(BlockAccessListValidationError::DuplicateBlockAccessIndex {
292 address,
293 kind,
294 index,
295 });
296 }
297 if previous > index {
298 return Err(BlockAccessListValidationError::ChangeIndicesOutOfOrder {
299 address,
300 kind,
301 previous,
302 index,
303 });
304 }
305 }
306 previous = Some(index);
307 }
308 Ok(())
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::{BalanceChange, CodeChange, NonceChange, SlotChanges, StorageChange};
315 use alloc::vec;
316 use alloy_primitives::{Bytes, U256};
317
318 const fn index(value: u64) -> BlockAccessIndex {
319 BlockAccessIndex::new(value)
320 }
321
322 fn account(address: u8) -> AccountChanges {
323 AccountChanges {
324 address: Address::with_last_byte(address),
325 storage_changes: vec![SlotChanges::new(
326 U256::from(1),
327 vec![StorageChange::new(index(0), U256::from(10))],
328 )],
329 storage_reads: vec![U256::from(2)],
330 balance_changes: vec![BalanceChange::new(index(1), U256::from(20))],
331 nonce_changes: vec![NonceChange::new(index(2), 1)],
332 code_changes: vec![CodeChange::new(index(3), Bytes::new())],
333 }
334 }
335
336 #[test]
337 fn accepts_canonical_block_access_list() {
338 let bal = crate::bal::Bal::new(vec![account(1), account(2)]);
339
340 assert_eq!(validate_block_access_list(bal.as_slice(), 2), Ok(()));
341 assert_eq!(bal.validate_structure(2), Ok(()));
342 }
343
344 #[test]
345 fn rejects_duplicate_and_out_of_order_accounts() {
346 let address = Address::with_last_byte(2);
347 assert_eq!(
348 validate_block_access_list(&[account(2), account(2)], 2),
349 Err(BlockAccessListValidationError::DuplicateAccount { address })
350 );
351
352 let previous = Address::with_last_byte(2);
353 let address = Address::with_last_byte(1);
354 assert_eq!(
355 validate_block_access_list(&[account(2), account(1)], 2),
356 Err(BlockAccessListValidationError::AccountsOutOfOrder { previous, address })
357 );
358 }
359
360 #[test]
361 fn rejects_invalid_storage_entries() {
362 let address = Address::with_last_byte(1);
363 let slot = U256::from(1);
364 let mut empty = account(1);
365 empty.storage_changes[0].changes.clear();
366 assert_eq!(
367 validate_block_access_list(&[empty], 2),
368 Err(BlockAccessListValidationError::EmptyStorageChanges { address, slot })
369 );
370
371 let mut duplicate = account(1);
372 duplicate.storage_reads.insert(0, slot);
373 assert_eq!(
374 validate_block_access_list(&[duplicate], 2),
375 Err(BlockAccessListValidationError::DuplicateStorageKey { address, slot })
376 );
377
378 let mut out_of_order = account(1);
379 out_of_order.storage_reads = vec![U256::from(3), U256::from(2)];
380 assert_eq!(
381 validate_block_access_list(&[out_of_order], 2),
382 Err(BlockAccessListValidationError::StorageKeysOutOfOrder {
383 address,
384 previous: U256::from(3),
385 slot: U256::from(2),
386 })
387 );
388 }
389
390 #[test]
391 fn rejects_invalid_change_indices() {
392 let address = Address::with_last_byte(1);
393 let mut duplicate = account(1);
394 duplicate.balance_changes.push(BalanceChange::new(index(1), U256::from(30)));
395 assert_eq!(
396 validate_block_access_list(&[duplicate], 2),
397 Err(BlockAccessListValidationError::DuplicateBlockAccessIndex {
398 address,
399 kind: BlockAccessListChangeKind::Balance,
400 index: index(1),
401 })
402 );
403
404 let mut out_of_order = account(1);
405 out_of_order.nonce_changes =
406 vec![NonceChange::new(index(2), 1), NonceChange::new(index(1), 2)];
407 assert_eq!(
408 validate_block_access_list(&[out_of_order], 2),
409 Err(BlockAccessListValidationError::ChangeIndicesOutOfOrder {
410 address,
411 kind: BlockAccessListChangeKind::Nonce,
412 previous: index(2),
413 index: index(1),
414 })
415 );
416
417 let mut exceeds_u32 = account(1);
418 exceeds_u32.code_changes[0].block_access_index = index(u32::MAX as u64 + 1);
419 assert_eq!(
420 validate_block_access_list(&[exceeds_u32], usize::MAX),
421 Err(BlockAccessListValidationError::BlockAccessIndexOutOfRange {
422 address,
423 kind: BlockAccessListChangeKind::Code,
424 index: index(u32::MAX as u64 + 1),
425 })
426 );
427
428 let mut exceeds_block = account(1);
429 exceeds_block.storage_changes[0].changes[0].block_access_index = index(4);
430 assert_eq!(
431 validate_block_access_list(&[exceeds_block], 2),
432 Err(BlockAccessListValidationError::BlockAccessIndexExceedsBlock {
433 address,
434 kind: BlockAccessListChangeKind::Storage,
435 index: index(4),
436 max: index(3),
437 })
438 );
439 }
440}