1#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6 issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
10
11use alloy_consensus::EMPTY_OMMER_ROOT_HASH;
12use alloy_eips::merge::ALLOWED_FUTURE_BLOCK_TIME_SECONDS;
13use alloy_primitives::U256;
14use reth_chainspec::{EthChainSpec, EthereumHardforks};
15use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator};
16use reth_consensus_common::validation::{
17 validate_4844_header_standalone, validate_against_parent_4844,
18 validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number,
19 validate_against_parent_timestamp, validate_block_pre_execution, validate_body_against_header,
20 validate_header_base_fee, validate_header_extra_data, validate_header_gas,
21};
22use reth_execution_types::BlockExecutionResult;
23use reth_primitives_traits::{
24 constants::{GAS_LIMIT_BOUND_DIVISOR, MINIMUM_GAS_LIMIT},
25 Block, BlockHeader, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader,
26};
27use std::{fmt::Debug, sync::Arc, time::SystemTime};
28
29mod validation;
30pub use validation::validate_block_post_execution;
31
32#[derive(Debug, Clone)]
36pub struct EthBeaconConsensus<ChainSpec> {
37 chain_spec: Arc<ChainSpec>,
39}
40
41impl<ChainSpec: EthChainSpec + EthereumHardforks> EthBeaconConsensus<ChainSpec> {
42 pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
44 Self { chain_spec }
45 }
46
47 fn validate_against_parent_gas_limit<H: BlockHeader>(
52 &self,
53 header: &SealedHeader<H>,
54 parent: &SealedHeader<H>,
55 ) -> Result<(), ConsensusError> {
56 let parent_gas_limit = if !self.chain_spec.is_london_active_at_block(parent.number()) &&
58 self.chain_spec.is_london_active_at_block(header.number())
59 {
60 parent.gas_limit() *
61 self.chain_spec
62 .base_fee_params_at_timestamp(header.timestamp())
63 .elasticity_multiplier as u64
64 } else {
65 parent.gas_limit()
66 };
67
68 if header.gas_limit() > parent_gas_limit {
70 if header.gas_limit() - parent_gas_limit >= parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR {
71 return Err(ConsensusError::GasLimitInvalidIncrease {
72 parent_gas_limit,
73 child_gas_limit: header.gas_limit(),
74 })
75 }
76 }
77 else if parent_gas_limit - header.gas_limit() >=
79 parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR
80 {
81 return Err(ConsensusError::GasLimitInvalidDecrease {
82 parent_gas_limit,
83 child_gas_limit: header.gas_limit(),
84 })
85 }
86 else if header.gas_limit() < MINIMUM_GAS_LIMIT {
88 return Err(ConsensusError::GasLimitInvalidMinimum {
89 child_gas_limit: header.gas_limit(),
90 })
91 }
92
93 Ok(())
94 }
95}
96
97impl<ChainSpec, N> FullConsensus<N> for EthBeaconConsensus<ChainSpec>
98where
99 ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug,
100 N: NodePrimitives,
101{
102 fn validate_block_post_execution(
103 &self,
104 block: &RecoveredBlock<N::Block>,
105 result: &BlockExecutionResult<N::Receipt>,
106 ) -> Result<(), ConsensusError> {
107 validate_block_post_execution(block, &self.chain_spec, &result.receipts, &result.requests)
108 }
109}
110
111impl<B, ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> Consensus<B>
112 for EthBeaconConsensus<ChainSpec>
113where
114 B: Block,
115{
116 type Error = ConsensusError;
117
118 fn validate_body_against_header(
119 &self,
120 body: &B::Body,
121 header: &SealedHeader<B::Header>,
122 ) -> Result<(), Self::Error> {
123 validate_body_against_header(body, header.header())
124 }
125
126 fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), Self::Error> {
127 validate_block_pre_execution(block, &self.chain_spec)
128 }
129}
130
131impl<H, ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderValidator<H>
132 for EthBeaconConsensus<ChainSpec>
133where
134 H: BlockHeader,
135{
136 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError> {
137 validate_header_gas(header.header())?;
138 validate_header_base_fee(header.header(), &self.chain_spec)?;
139
140 if self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) &&
142 header.withdrawals_root().is_none()
143 {
144 return Err(ConsensusError::WithdrawalsRootMissing)
145 } else if !self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) &&
146 header.withdrawals_root().is_some()
147 {
148 return Err(ConsensusError::WithdrawalsRootUnexpected)
149 }
150
151 if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp()) {
153 validate_4844_header_standalone(header.header())?;
154 } else if header.blob_gas_used().is_some() {
155 return Err(ConsensusError::BlobGasUsedUnexpected)
156 } else if header.excess_blob_gas().is_some() {
157 return Err(ConsensusError::ExcessBlobGasUnexpected)
158 } else if header.parent_beacon_block_root().is_some() {
159 return Err(ConsensusError::ParentBeaconBlockRootUnexpected)
160 }
161
162 if self.chain_spec.is_prague_active_at_timestamp(header.timestamp()) {
163 if header.requests_hash().is_none() {
164 return Err(ConsensusError::RequestsHashMissing)
165 }
166 } else if header.requests_hash().is_some() {
167 return Err(ConsensusError::RequestsHashUnexpected)
168 }
169
170 Ok(())
171 }
172
173 fn validate_header_against_parent(
174 &self,
175 header: &SealedHeader<H>,
176 parent: &SealedHeader<H>,
177 ) -> Result<(), ConsensusError> {
178 validate_against_parent_hash_number(header.header(), parent)?;
179
180 validate_against_parent_timestamp(header.header(), parent.header())?;
181
182 self.validate_against_parent_gas_limit(header, parent)?;
185
186 validate_against_parent_eip1559_base_fee(
187 header.header(),
188 parent.header(),
189 &self.chain_spec,
190 )?;
191
192 if let Some(blob_params) = self.chain_spec.blob_params_at_timestamp(header.timestamp()) {
194 validate_against_parent_4844(header.header(), parent.header(), blob_params)?;
195 }
196
197 Ok(())
198 }
199
200 fn validate_header_with_total_difficulty(
201 &self,
202 header: &H,
203 _total_difficulty: U256,
204 ) -> Result<(), ConsensusError> {
205 let is_post_merge = self.chain_spec.is_paris_active_at_block(header.number());
206
207 if is_post_merge {
208 if !header.difficulty().is_zero() {
209 return Err(ConsensusError::TheMergeDifficultyIsNotZero)
210 }
211
212 if !header.nonce().is_some_and(|nonce| nonce.is_zero()) {
213 return Err(ConsensusError::TheMergeNonceIsNotZero)
214 }
215
216 if header.ommers_hash() != EMPTY_OMMER_ROOT_HASH {
217 return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty)
218 }
219
220 validate_header_extra_data(header)?;
230
231 } else {
234 let present_timestamp =
240 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
241
242 if header.timestamp() > present_timestamp + ALLOWED_FUTURE_BLOCK_TIME_SECONDS {
243 return Err(ConsensusError::TimestampIsInFuture {
244 timestamp: header.timestamp(),
245 present_timestamp,
246 })
247 }
248
249 validate_header_extra_data(header)?;
250 }
251
252 Ok(())
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use alloy_primitives::B256;
260 use reth_chainspec::{ChainSpec, ChainSpecBuilder};
261 use reth_primitives_traits::proofs;
262
263 fn header_with_gas_limit(gas_limit: u64) -> SealedHeader {
264 let header = reth_primitives_traits::Header { gas_limit, ..Default::default() };
265 SealedHeader::new(header, B256::ZERO)
266 }
267
268 #[test]
269 fn test_valid_gas_limit_increase() {
270 let parent = header_with_gas_limit(GAS_LIMIT_BOUND_DIVISOR * 10);
271 let child = header_with_gas_limit((parent.gas_limit + 5) as u64);
272
273 assert_eq!(
274 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
275 .validate_against_parent_gas_limit(&child, &parent),
276 Ok(())
277 );
278 }
279
280 #[test]
281 fn test_gas_limit_below_minimum() {
282 let parent = header_with_gas_limit(MINIMUM_GAS_LIMIT);
283 let child = header_with_gas_limit(MINIMUM_GAS_LIMIT - 1);
284
285 assert_eq!(
286 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
287 .validate_against_parent_gas_limit(&child, &parent),
288 Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: child.gas_limit as u64 })
289 );
290 }
291
292 #[test]
293 fn test_invalid_gas_limit_increase_exceeding_limit() {
294 let parent = header_with_gas_limit(GAS_LIMIT_BOUND_DIVISOR * 10);
295 let child = header_with_gas_limit(
296 parent.gas_limit + parent.gas_limit / GAS_LIMIT_BOUND_DIVISOR + 1,
297 );
298
299 assert_eq!(
300 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
301 .validate_against_parent_gas_limit(&child, &parent),
302 Err(ConsensusError::GasLimitInvalidIncrease {
303 parent_gas_limit: parent.gas_limit,
304 child_gas_limit: child.gas_limit,
305 })
306 );
307 }
308
309 #[test]
310 fn test_valid_gas_limit_decrease_within_limit() {
311 let parent = header_with_gas_limit(GAS_LIMIT_BOUND_DIVISOR * 10);
312 let child = header_with_gas_limit(parent.gas_limit - 5);
313
314 assert_eq!(
315 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
316 .validate_against_parent_gas_limit(&child, &parent),
317 Ok(())
318 );
319 }
320
321 #[test]
322 fn test_invalid_gas_limit_decrease_exceeding_limit() {
323 let parent = header_with_gas_limit(GAS_LIMIT_BOUND_DIVISOR * 10);
324 let child = header_with_gas_limit(
325 parent.gas_limit - parent.gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1,
326 );
327
328 assert_eq!(
329 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
330 .validate_against_parent_gas_limit(&child, &parent),
331 Err(ConsensusError::GasLimitInvalidDecrease {
332 parent_gas_limit: parent.gas_limit,
333 child_gas_limit: child.gas_limit,
334 })
335 );
336 }
337
338 #[test]
339 fn shanghai_block_zero_withdrawals() {
340 let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());
343
344 let header = reth_primitives_traits::Header {
345 base_fee_per_gas: Some(1337),
346 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
347 ..Default::default()
348 };
349
350 assert_eq!(
351 EthBeaconConsensus::new(chain_spec).validate_header(&SealedHeader::seal_slow(header,)),
352 Ok(())
353 );
354 }
355}