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