1use crate::constants::{GAS_PER_BLOB, MAX_RLP_BLOCK_SIZE, POST_OSAKA_GAS_LIMIT_CAP};
7use crate::errors::InvalidBlockError;
8use crate::types::block_access_list::BlockAccessList;
9use crate::types::requests::{EncodedRequests, Requests, compute_requests_hash};
10use crate::types::{
11 Block, BlockHeader, ChainConfig, EIP4844Transaction, Receipt, Transaction,
12 compute_receipts_root_and_logs_bloom, validate_block_header, validate_cancun_header_fields,
13 validate_prague_header_fields, validate_pre_cancun_header_fields,
14};
15use ethrex_crypto::{Crypto, NativeCrypto};
16use ethrex_rlp::encode::RLPEncode;
17
18pub fn validate_block_pre_execution(
29 block: &Block,
30 parent_header: &BlockHeader,
31 chain_config: &ChainConfig,
32 elasticity_multiplier: u64,
33) -> Result<(), InvalidBlockError> {
34 validate_block_header(&block.header, parent_header, elasticity_multiplier)?;
36
37 if chain_config.is_osaka_activated(block.header.timestamp) {
38 let block_rlp_size = block.length();
39 if block_rlp_size > MAX_RLP_BLOCK_SIZE as usize {
40 return Err(InvalidBlockError::MaximumRlpSizeExceeded(
41 MAX_RLP_BLOCK_SIZE,
42 block_rlp_size as u64,
43 ));
44 }
45 }
46 if chain_config.is_prague_activated(block.header.timestamp) {
47 validate_prague_header_fields(&block.header, parent_header, chain_config)?;
48 verify_blob_gas_usage(block, chain_config)?;
49 if chain_config.is_osaka_activated(block.header.timestamp)
50 && !chain_config.is_amsterdam_activated(block.header.timestamp)
51 {
52 verify_transaction_max_gas_limit(block)?;
53 }
54 } else if chain_config.is_cancun_activated(block.header.timestamp) {
55 validate_cancun_header_fields(&block.header, parent_header, chain_config)?;
56 verify_blob_gas_usage(block, chain_config)?;
57 } else {
58 validate_pre_cancun_header_fields(&block.header)?;
59 }
60
61 for tx in &block.body.transactions {
74 if matches!(tx, Transaction::PrivilegedL2Transaction(_)) {
75 continue;
76 }
77 if let Some(tx_chain_id) = tx.chain_id()
78 && tx_chain_id != chain_config.chain_id
79 {
80 return Err(InvalidBlockError::InvalidTransactionChainId {
81 have: tx_chain_id,
82 want: chain_config.chain_id,
83 });
84 }
85 }
86
87 Ok(())
88}
89
90pub fn validate_gas_used(
94 block_gas_used: u64,
95 block_header: &BlockHeader,
96) -> Result<(), InvalidBlockError> {
97 if block_gas_used != block_header.gas_used {
98 return Err(InvalidBlockError::GasUsedMismatch(
99 block_gas_used,
100 block_header.gas_used,
101 ));
102 }
103 Ok(())
104}
105
106pub fn validate_receipts_root_and_logs_bloom(
115 block_header: &BlockHeader,
116 receipts: &[Receipt],
117 crypto: &dyn Crypto,
118) -> Result<(), InvalidBlockError> {
119 let (receipts_root, logs_bloom) = compute_receipts_root_and_logs_bloom(receipts, crypto);
120
121 if receipts_root != block_header.receipts_root {
122 return Err(InvalidBlockError::ReceiptsRootMismatch);
123 }
124 if logs_bloom != block_header.logs_bloom {
125 return Err(InvalidBlockError::LogsBloomMismatch);
126 }
127 Ok(())
128}
129
130pub fn validate_requests_hash(
132 header: &BlockHeader,
133 chain_config: &ChainConfig,
134 requests: &[Requests],
135) -> Result<(), InvalidBlockError> {
136 if !chain_config.is_prague_activated(header.timestamp) {
137 return Ok(());
138 }
139
140 let encoded_requests: Vec<EncodedRequests> = requests.iter().map(|r| r.encode()).collect();
141 let computed_requests_hash = compute_requests_hash(&encoded_requests);
142 let valid = header
143 .requests_hash
144 .map(|requests_hash| requests_hash == computed_requests_hash)
145 .unwrap_or(false);
146
147 if !valid {
148 return Err(InvalidBlockError::RequestsHashMismatch);
149 }
150
151 Ok(())
152}
153
154fn validate_bal_indices(
156 indices: impl Iterator<Item = u32>,
157 max_valid_index: u32,
158) -> Result<(), InvalidBlockError> {
159 for index in indices {
160 if index > max_valid_index {
161 return Err(InvalidBlockError::BlockAccessListIndexOutOfBounds {
162 index,
163 max: max_valid_index,
164 });
165 }
166 }
167 Ok(())
168}
169
170pub fn validate_header_bal_indices(
175 bal: &crate::types::block_access_list::BlockAccessList,
176 transaction_count: usize,
177) -> Result<(), InvalidBlockError> {
178 let max_valid_index = u32::try_from(transaction_count + 1).unwrap_or(u32::MAX);
179
180 for account in bal.accounts() {
181 validate_bal_indices(
182 account
183 .storage_changes
184 .iter()
185 .flat_map(|slot| slot.slot_changes.iter().map(|c| c.block_access_index)),
186 max_valid_index,
187 )?;
188 validate_bal_indices(
189 account.balance_changes.iter().map(|c| c.block_access_index),
190 max_valid_index,
191 )?;
192 validate_bal_indices(
193 account.nonce_changes.iter().map(|c| c.block_access_index),
194 max_valid_index,
195 )?;
196 validate_bal_indices(
197 account.code_changes.iter().map(|c| c.block_access_index),
198 max_valid_index,
199 )?;
200 }
201 Ok(())
202}
203
204pub fn validate_bal_code_sizes(
211 bal: &BlockAccessList,
212 max_code_size: u64,
213) -> Result<(), InvalidBlockError> {
214 for account in bal.accounts() {
215 for change in &account.code_changes {
216 let size = change.new_code.len();
217 if u64::try_from(size).map_or(true, |size| size > max_code_size) {
218 return Err(InvalidBlockError::BlockAccessListCodeTooLarge {
219 address: account.address,
220 size,
221 max: max_code_size,
222 });
223 }
224 }
225 }
226 Ok(())
227}
228
229pub fn validate_block_access_list_hash(
233 header: &BlockHeader,
234 chain_config: &ChainConfig,
235 computed_bal: &crate::types::block_access_list::BlockAccessList,
236 transaction_count: usize,
237 crypto: &dyn Crypto,
238) -> Result<(), InvalidBlockError> {
239 use crate::constants::BAL_ITEM_COST;
240
241 if !chain_config.is_amsterdam_activated(header.timestamp) {
243 return Ok(());
244 }
245
246 let max_valid_index = u32::try_from(transaction_count + 1).unwrap_or(u32::MAX);
249
250 let mut bal_items: u64 = 0;
252 for account in computed_bal.accounts() {
253 bal_items += 1; bal_items += account.storage_reads.len() as u64;
255 bal_items += account.storage_changes.len() as u64;
256
257 validate_bal_indices(
259 account
260 .storage_changes
261 .iter()
262 .flat_map(|slot| slot.slot_changes.iter().map(|c| c.block_access_index)),
263 max_valid_index,
264 )?;
265
266 validate_bal_indices(
268 account.balance_changes.iter().map(|c| c.block_access_index),
269 max_valid_index,
270 )?;
271
272 validate_bal_indices(
274 account.nonce_changes.iter().map(|c| c.block_access_index),
275 max_valid_index,
276 )?;
277
278 validate_bal_indices(
280 account.code_changes.iter().map(|c| c.block_access_index),
281 max_valid_index,
282 )?;
283 }
284
285 let max_items = header.gas_limit / BAL_ITEM_COST;
287 if bal_items > max_items {
288 return Err(InvalidBlockError::BlockAccessListSizeExceeded {
289 items: bal_items,
290 max_items,
291 });
292 }
293
294 let computed_hash = computed_bal.compute_hash(crypto);
295 let valid = header
296 .block_access_list_hash
297 .map(|expected_hash| expected_hash == computed_hash)
298 .unwrap_or(false);
299
300 if !valid {
301 return Err(InvalidBlockError::BlockAccessListHashMismatch);
302 }
303
304 Ok(())
305}
306
307pub fn validate_block_access_list_size(
313 header: &BlockHeader,
314 chain_config: &ChainConfig,
315 computed_bal: &crate::types::block_access_list::BlockAccessList,
316) -> Result<(), InvalidBlockError> {
317 use crate::constants::BAL_ITEM_COST;
318
319 if !chain_config.is_amsterdam_activated(header.timestamp) {
320 return Ok(());
321 }
322
323 let bal_items = computed_bal.item_count();
324 let max_items = header.gas_limit / BAL_ITEM_COST;
325
326 if bal_items > max_items {
327 return Err(InvalidBlockError::BlockAccessListSizeExceeded {
328 items: bal_items,
329 max_items,
330 });
331 }
332
333 Ok(())
334}
335
336pub fn verify_blob_gas_usage(block: &Block, config: &ChainConfig) -> Result<(), InvalidBlockError> {
339 let mut blob_gas_used = 0_u32;
340 let mut blobs_in_block = 0_u32;
341 let max_blob_number_per_block = config
342 .get_fork_blob_schedule(block.header.timestamp)
343 .map(|schedule| schedule.max)
344 .ok_or(InvalidBlockError::InvalidBlockFork)?;
345 let max_blob_gas_per_block = max_blob_number_per_block * GAS_PER_BLOB;
346
347 for transaction in block.body.transactions.iter() {
348 match transaction {
349 crate::types::Transaction::EIP4844Transaction(tx) => {
350 blob_gas_used += get_total_blob_gas(tx);
351 blobs_in_block += tx.blob_versioned_hashes.len() as u32;
352 }
353 crate::types::Transaction::FrameTransaction(tx) => {
354 blob_gas_used += GAS_PER_BLOB * tx.blob_versioned_hashes.len() as u32;
355 blobs_in_block += tx.blob_versioned_hashes.len() as u32;
356 }
357 _ => {}
358 }
359 }
360 if blob_gas_used > max_blob_gas_per_block {
361 return Err(InvalidBlockError::ExceededMaxBlobGasPerBlock);
362 }
363 if blobs_in_block > max_blob_number_per_block {
364 return Err(InvalidBlockError::ExceededMaxBlobNumberPerBlock);
365 }
366 if block
367 .header
368 .blob_gas_used
369 .is_some_and(|header_blob_gas_used| header_blob_gas_used != blob_gas_used as u64)
370 {
371 return Err(InvalidBlockError::BlobGasUsedMismatch);
372 }
373 Ok(())
374}
375
376fn verify_transaction_max_gas_limit(block: &Block) -> Result<(), InvalidBlockError> {
380 for transaction in block.body.transactions.iter() {
381 if transaction.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP {
382 return Err(InvalidBlockError::InvalidTransaction(format!(
383 "Transaction gas limit exceeds maximum. Transaction hash: {}, transaction gas limit: {}",
384 transaction.hash(&NativeCrypto),
385 transaction.gas_limit()
386 )));
387 }
388 }
389 Ok(())
390}
391
392pub fn get_total_blob_gas(tx: &EIP4844Transaction) -> u32 {
394 GAS_PER_BLOB * tx.blob_versioned_hashes.len() as u32
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400 use crate::H256;
401 use crate::types::{Block, BlockBody, BlockHeader, ChainConfig, Transaction};
402
403 fn cancun_config() -> ChainConfig {
406 ChainConfig {
407 cancun_time: Some(0),
408 ..Default::default()
409 }
410 }
411
412 fn make_block(transactions: Vec<Transaction>, blob_gas_used: u64) -> Block {
415 Block {
416 header: BlockHeader {
417 timestamp: 1,
418 gas_limit: 30_000_000,
419 blob_gas_used: Some(blob_gas_used),
420 excess_blob_gas: Some(0),
421 ..Default::default()
422 },
423 body: BlockBody {
424 transactions,
425 ommers: vec![],
426 withdrawals: Some(vec![]),
427 },
428 }
429 }
430
431 #[test]
434 fn eip4844_blob_gas_counts_correctly() {
435 let config = cancun_config();
436 let tx = Transaction::EIP4844Transaction(EIP4844Transaction {
437 blob_versioned_hashes: vec![H256::zero(), H256::zero()],
438 ..Default::default()
439 });
440 let block = make_block(vec![tx], 2 * GAS_PER_BLOB as u64);
441 assert!(verify_blob_gas_usage(&block, &config).is_ok());
442 }
443
444 #[test]
445 fn eip4844_blob_gas_mismatch_fails() {
446 let config = cancun_config();
447 let tx = Transaction::EIP4844Transaction(EIP4844Transaction {
448 blob_versioned_hashes: vec![H256::zero(), H256::zero()],
449 ..Default::default()
450 });
451 let block = make_block(vec![tx], 0);
453 assert!(matches!(
454 verify_blob_gas_usage(&block, &config),
455 Err(InvalidBlockError::BlobGasUsedMismatch)
456 ));
457 }
458}