1use alloy_consensus::{
4 constants::MAXIMUM_EXTRA_DATA_SIZE, BlockHeader as _, EMPTY_OMMER_ROOT_HASH,
5};
6use alloy_eips::{calc_next_block_base_fee, eip4844::DATA_GAS_PER_BLOB, eip7840::BlobParams};
7use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks};
8use reth_consensus::ConsensusError;
9use reth_primitives_traits::{
10 constants::MAXIMUM_GAS_LIMIT_BLOCK, Block, BlockBody, BlockHeader, GotExpected, SealedBlock,
11 SealedHeader,
12};
13
14#[inline]
16pub fn validate_header_gas<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
17 if header.gas_used() > header.gas_limit() {
18 return Err(ConsensusError::HeaderGasUsedExceedsGasLimit {
19 gas_used: header.gas_used(),
20 gas_limit: header.gas_limit(),
21 })
22 }
23 if header.gas_limit() > MAXIMUM_GAS_LIMIT_BLOCK {
25 return Err(ConsensusError::HeaderGasLimitExceedsMax { gas_limit: header.gas_limit() })
26 }
27 Ok(())
28}
29
30#[inline]
32pub fn validate_header_base_fee<H: BlockHeader, ChainSpec: EthereumHardforks>(
33 header: &H,
34 chain_spec: &ChainSpec,
35) -> Result<(), ConsensusError> {
36 if chain_spec.is_london_active_at_block(header.number()) && header.base_fee_per_gas().is_none()
37 {
38 return Err(ConsensusError::BaseFeeMissing)
39 }
40 Ok(())
41}
42
43#[inline]
49pub fn validate_shanghai_withdrawals<B: Block>(
50 block: &SealedBlock<B>,
51) -> Result<(), ConsensusError> {
52 let withdrawals = block.body().withdrawals().ok_or(ConsensusError::BodyWithdrawalsMissing)?;
53 let withdrawals_root = alloy_consensus::proofs::calculate_withdrawals_root(withdrawals);
54 let header_withdrawals_root =
55 block.withdrawals_root().ok_or(ConsensusError::WithdrawalsRootMissing)?;
56 if withdrawals_root != *header_withdrawals_root {
57 return Err(ConsensusError::BodyWithdrawalsRootDiff(
58 GotExpected { got: withdrawals_root, expected: header_withdrawals_root }.into(),
59 ));
60 }
61 Ok(())
62}
63
64#[inline]
70pub fn validate_cancun_gas<B: Block>(block: &SealedBlock<B>) -> Result<(), ConsensusError> {
71 let header_blob_gas_used = block.blob_gas_used().ok_or(ConsensusError::BlobGasUsedMissing)?;
74 let total_blob_gas = block.body().blob_gas_used();
75 if total_blob_gas != header_blob_gas_used {
76 return Err(ConsensusError::BlobGasUsedDiff(GotExpected {
77 got: header_blob_gas_used,
78 expected: total_blob_gas,
79 }));
80 }
81 Ok(())
82}
83
84pub fn validate_body_against_header<B, H>(body: &B, header: &H) -> Result<(), ConsensusError>
91where
92 B: BlockBody,
93 H: BlockHeader,
94{
95 let ommers_hash = body.calculate_ommers_root();
96 if Some(header.ommers_hash()) != ommers_hash {
97 return Err(ConsensusError::BodyOmmersHashDiff(
98 GotExpected {
99 got: ommers_hash.unwrap_or(EMPTY_OMMER_ROOT_HASH),
100 expected: header.ommers_hash(),
101 }
102 .into(),
103 ))
104 }
105
106 let tx_root = body.calculate_tx_root();
107 if header.transactions_root() != tx_root {
108 return Err(ConsensusError::BodyTransactionRootDiff(
109 GotExpected { got: tx_root, expected: header.transactions_root() }.into(),
110 ))
111 }
112
113 match (header.withdrawals_root(), body.calculate_withdrawals_root()) {
114 (Some(header_withdrawals_root), Some(withdrawals_root)) => {
115 if withdrawals_root != header_withdrawals_root {
116 return Err(ConsensusError::BodyWithdrawalsRootDiff(
117 GotExpected { got: withdrawals_root, expected: header_withdrawals_root }.into(),
118 ))
119 }
120 }
121 (None, None) => {
122 }
124 _ => return Err(ConsensusError::WithdrawalsRootUnexpected),
125 }
126
127 Ok(())
128}
129
130pub fn validate_block_pre_execution<B, ChainSpec>(
137 block: &SealedBlock<B>,
138 chain_spec: &ChainSpec,
139) -> Result<(), ConsensusError>
140where
141 B: Block,
142 ChainSpec: EthereumHardforks,
143{
144 let ommers_hash = block.body().calculate_ommers_root();
146 if Some(block.ommers_hash()) != ommers_hash {
147 return Err(ConsensusError::BodyOmmersHashDiff(
148 GotExpected {
149 got: ommers_hash.unwrap_or(EMPTY_OMMER_ROOT_HASH),
150 expected: block.ommers_hash(),
151 }
152 .into(),
153 ))
154 }
155
156 if let Err(error) = block.ensure_transaction_root_valid() {
158 return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
159 }
160
161 if chain_spec.is_shanghai_active_at_timestamp(block.timestamp()) {
163 validate_shanghai_withdrawals(block)?;
164 }
165
166 if chain_spec.is_cancun_active_at_timestamp(block.timestamp()) {
167 validate_cancun_gas(block)?;
168 }
169
170 Ok(())
171}
172
173pub fn validate_4844_header_standalone<H: BlockHeader>(
184 header: &H,
185 blob_params: BlobParams,
186) -> Result<(), ConsensusError> {
187 let blob_gas_used = header.blob_gas_used().ok_or(ConsensusError::BlobGasUsedMissing)?;
188 let excess_blob_gas = header.excess_blob_gas().ok_or(ConsensusError::ExcessBlobGasMissing)?;
189
190 if header.parent_beacon_block_root().is_none() {
191 return Err(ConsensusError::ParentBeaconBlockRootMissing)
192 }
193
194 if blob_gas_used % DATA_GAS_PER_BLOB != 0 {
195 return Err(ConsensusError::BlobGasUsedNotMultipleOfBlobGasPerBlob {
196 blob_gas_used,
197 blob_gas_per_blob: DATA_GAS_PER_BLOB,
198 })
199 }
200
201 if excess_blob_gas % DATA_GAS_PER_BLOB != 0 {
204 return Err(ConsensusError::ExcessBlobGasNotMultipleOfBlobGasPerBlob {
205 excess_blob_gas,
206 blob_gas_per_blob: DATA_GAS_PER_BLOB,
207 })
208 }
209
210 if blob_gas_used > blob_params.max_blob_gas_per_block() {
211 return Err(ConsensusError::BlobGasUsedExceedsMaxBlobGasPerBlock {
212 blob_gas_used,
213 max_blob_gas_per_block: blob_params.max_blob_gas_per_block(),
214 })
215 }
216
217 Ok(())
218}
219
220#[inline]
225pub fn validate_header_extra_data<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
226 let extra_data_len = header.extra_data().len();
227 if extra_data_len > MAXIMUM_EXTRA_DATA_SIZE {
228 Err(ConsensusError::ExtraDataExceedsMax { len: extra_data_len })
229 } else {
230 Ok(())
231 }
232}
233
234#[inline]
239pub fn validate_against_parent_hash_number<H: BlockHeader>(
240 header: &H,
241 parent: &SealedHeader<H>,
242) -> Result<(), ConsensusError> {
243 if parent.number() + 1 != header.number() {
245 return Err(ConsensusError::ParentBlockNumberMismatch {
246 parent_block_number: parent.number(),
247 block_number: header.number(),
248 })
249 }
250
251 if parent.hash() != header.parent_hash() {
252 return Err(ConsensusError::ParentHashMismatch(
253 GotExpected { got: header.parent_hash(), expected: parent.hash() }.into(),
254 ))
255 }
256
257 Ok(())
258}
259
260#[inline]
262pub fn validate_against_parent_eip1559_base_fee<
263 H: BlockHeader,
264 ChainSpec: EthChainSpec + EthereumHardforks,
265>(
266 header: &H,
267 parent: &H,
268 chain_spec: &ChainSpec,
269) -> Result<(), ConsensusError> {
270 if chain_spec.is_london_active_at_block(header.number()) {
271 let base_fee = header.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?;
272
273 let expected_base_fee = if chain_spec
274 .ethereum_fork_activation(EthereumHardfork::London)
275 .transitions_at_block(header.number())
276 {
277 alloy_eips::eip1559::INITIAL_BASE_FEE
278 } else {
279 let base_fee = parent.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?;
282 calc_next_block_base_fee(
283 parent.gas_used(),
284 parent.gas_limit(),
285 base_fee,
286 chain_spec.base_fee_params_at_timestamp(header.timestamp()),
287 )
288 };
289 if expected_base_fee != base_fee {
290 return Err(ConsensusError::BaseFeeDiff(GotExpected {
291 expected: expected_base_fee,
292 got: base_fee,
293 }))
294 }
295 }
296
297 Ok(())
298}
299
300#[inline]
302pub fn validate_against_parent_timestamp<H: BlockHeader>(
303 header: &H,
304 parent: &H,
305) -> Result<(), ConsensusError> {
306 if header.timestamp() <= parent.timestamp() {
307 return Err(ConsensusError::TimestampIsInPast {
308 parent_timestamp: parent.timestamp(),
309 timestamp: header.timestamp(),
310 })
311 }
312 Ok(())
313}
314
315pub fn validate_against_parent_4844<H: BlockHeader>(
320 header: &H,
321 parent: &H,
322 blob_params: BlobParams,
323) -> Result<(), ConsensusError> {
324 let parent_blob_gas_used = parent.blob_gas_used().unwrap_or(0);
331 let parent_excess_blob_gas = parent.excess_blob_gas().unwrap_or(0);
332
333 if header.blob_gas_used().is_none() {
334 return Err(ConsensusError::BlobGasUsedMissing)
335 }
336 let excess_blob_gas = header.excess_blob_gas().ok_or(ConsensusError::ExcessBlobGasMissing)?;
337
338 let expected_excess_blob_gas =
339 blob_params.next_block_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used);
340 if expected_excess_blob_gas != excess_blob_gas {
341 return Err(ConsensusError::ExcessBlobGasDiff {
342 diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas },
343 parent_excess_blob_gas,
344 parent_blob_gas_used,
345 })
346 }
347
348 Ok(())
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 use alloy_consensus::{BlockBody, Header, TxEip4844};
355 use alloy_eips::eip4895::Withdrawals;
356 use alloy_primitives::{Address, Bytes, Signature, U256};
357 use rand::Rng;
358 use reth_chainspec::ChainSpecBuilder;
359 use reth_ethereum_primitives::{Transaction, TransactionSigned};
360 use reth_primitives_traits::proofs;
361
362 fn mock_blob_tx(nonce: u64, num_blobs: usize) -> TransactionSigned {
363 let mut rng = rand::rng();
364 let request = Transaction::Eip4844(TxEip4844 {
365 chain_id: 1u64,
366 nonce,
367 max_fee_per_gas: 0x28f000fff,
368 max_priority_fee_per_gas: 0x28f000fff,
369 max_fee_per_blob_gas: 0x7,
370 gas_limit: 10,
371 to: Address::default(),
372 value: U256::from(3_u64),
373 input: Bytes::from(vec![1, 2]),
374 access_list: Default::default(),
375 blob_versioned_hashes: std::iter::repeat_with(|| rng.random())
376 .take(num_blobs)
377 .collect(),
378 });
379
380 let signature = Signature::new(U256::default(), U256::default(), true);
381
382 TransactionSigned::new_unhashed(request, signature)
383 }
384
385 #[test]
386 fn cancun_block_incorrect_blob_gas_used() {
387 let chain_spec = ChainSpecBuilder::mainnet().cancun_activated().build();
388
389 let transaction = mock_blob_tx(1, 10);
391
392 let header = Header {
393 base_fee_per_gas: Some(1337),
394 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
395 blob_gas_used: Some(1),
396 transactions_root: proofs::calculate_transaction_root(&[transaction.clone()]),
397 ..Default::default()
398 };
399 let body = BlockBody {
400 transactions: vec![transaction],
401 ommers: vec![],
402 withdrawals: Some(Withdrawals::default()),
403 };
404
405 let block = SealedBlock::seal_slow(alloy_consensus::Block { header, body });
406
407 let expected_blob_gas_used = 10 * DATA_GAS_PER_BLOB;
409
410 assert_eq!(
412 validate_block_pre_execution(&block, &chain_spec),
413 Err(ConsensusError::BlobGasUsedDiff(GotExpected {
414 got: 1,
415 expected: expected_blob_gas_used
416 }))
417 );
418 }
419}