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/SeismicSystems/seismic-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::{BlockHeader, EMPTY_OMMER_ROOT_HASH};
12use alloy_eips::merge::ALLOWED_FUTURE_BLOCK_TIME_SECONDS;
13use alloy_primitives::U256;
14use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks};
15use reth_consensus::{
16 Consensus, ConsensusError, FullConsensus, HeaderValidator, PostExecutionInput,
17};
18use reth_consensus_common::validation::{
19 validate_4844_header_standalone, validate_against_parent_4844,
20 validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number,
21 validate_against_parent_timestamp, validate_block_pre_execution, validate_body_against_header,
22 validate_header_base_fee, validate_header_extradata, validate_header_gas,
23};
24use reth_primitives::{BlockWithSenders, NodePrimitives, Receipt, SealedBlock, SealedHeader};
25use reth_primitives_traits::{constants::MINIMUM_GAS_LIMIT, BlockBody};
26use std::{fmt::Debug, sync::Arc, time::SystemTime};
27
28pub const GAS_LIMIT_BOUND_DIVISOR: u64 = 1024;
30
31mod validation;
32pub use validation::validate_block_post_execution;
33
34#[derive(Debug, Clone)]
38pub struct EthBeaconConsensus<ChainSpec> {
39 chain_spec: Arc<ChainSpec>,
41}
42
43impl<ChainSpec: EthChainSpec + EthereumHardforks> EthBeaconConsensus<ChainSpec> {
44 pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
46 Self { chain_spec }
47 }
48
49 fn validate_against_parent_gas_limit<H: BlockHeader>(
54 &self,
55 header: &SealedHeader<H>,
56 parent: &SealedHeader<H>,
57 ) -> Result<(), ConsensusError> {
58 let parent_gas_limit =
60 if self.chain_spec.fork(EthereumHardfork::London).transitions_at_block(header.number())
61 {
62 parent.gas_limit() *
63 self.chain_spec
64 .base_fee_params_at_timestamp(header.timestamp())
65 .elasticity_multiplier as u64
66 } else {
67 parent.gas_limit()
68 };
69
70 if header.gas_limit() > parent_gas_limit {
72 if header.gas_limit() - parent_gas_limit >= parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR {
73 return Err(ConsensusError::GasLimitInvalidIncrease {
74 parent_gas_limit,
75 child_gas_limit: header.gas_limit(),
76 })
77 }
78 }
79 else if parent_gas_limit - header.gas_limit() >=
81 parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR
82 {
83 return Err(ConsensusError::GasLimitInvalidDecrease {
84 parent_gas_limit,
85 child_gas_limit: header.gas_limit(),
86 })
87 }
88 else if header.gas_limit() < MINIMUM_GAS_LIMIT {
90 return Err(ConsensusError::GasLimitInvalidMinimum {
91 child_gas_limit: header.gas_limit(),
92 })
93 }
94
95 Ok(())
96 }
97}
98
99impl<ChainSpec, N> FullConsensus<N> for EthBeaconConsensus<ChainSpec>
100where
101 ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug,
102 N: NodePrimitives<Receipt = Receipt>,
103{
104 fn validate_block_post_execution(
105 &self,
106 block: &BlockWithSenders<N::Block>,
107 input: PostExecutionInput<'_>,
108 ) -> Result<(), ConsensusError> {
109 validate_block_post_execution(block, &self.chain_spec, input.receipts, input.requests)
110 }
111}
112
113impl<H, B, ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> Consensus<H, B>
114 for EthBeaconConsensus<ChainSpec>
115where
116 H: BlockHeader,
117 B: BlockBody,
118{
119 fn validate_body_against_header(
120 &self,
121 body: &B,
122 header: &SealedHeader<H>,
123 ) -> Result<(), ConsensusError> {
124 validate_body_against_header(body, header.header())
125 }
126
127 fn validate_block_pre_execution(
128 &self,
129 block: &SealedBlock<H, B>,
130 ) -> Result<(), ConsensusError> {
131 validate_block_pre_execution(block, &self.chain_spec)
132 }
133}
134
135impl<H, ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug> HeaderValidator<H>
136 for EthBeaconConsensus<ChainSpec>
137where
138 H: BlockHeader,
139{
140 fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError> {
141 validate_header_gas(header.header())?;
142 validate_header_base_fee(header.header(), &self.chain_spec)?;
143
144 if self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) &&
146 header.withdrawals_root().is_none()
147 {
148 return Err(ConsensusError::WithdrawalsRootMissing)
149 } else if !self.chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) &&
150 header.withdrawals_root().is_some()
151 {
152 return Err(ConsensusError::WithdrawalsRootUnexpected)
153 }
154
155 if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp()) {
157 validate_4844_header_standalone(header.header())?;
158 } else if header.blob_gas_used().is_some() {
159 return Err(ConsensusError::BlobGasUsedUnexpected)
160 } else if header.excess_blob_gas().is_some() {
161 return Err(ConsensusError::ExcessBlobGasUnexpected)
162 } else if header.parent_beacon_block_root().is_some() {
163 return Err(ConsensusError::ParentBeaconBlockRootUnexpected)
164 }
165
166 if self.chain_spec.is_prague_active_at_timestamp(header.timestamp()) {
167 if header.requests_hash().is_none() {
168 return Err(ConsensusError::RequestsHashMissing)
169 }
170 } else if header.requests_hash().is_some() {
171 return Err(ConsensusError::RequestsHashUnexpected)
172 }
173
174 Ok(())
175 }
176
177 fn validate_header_against_parent(
178 &self,
179 header: &SealedHeader<H>,
180 parent: &SealedHeader<H>,
181 ) -> Result<(), ConsensusError> {
182 validate_against_parent_hash_number(header.header(), parent)?;
183
184 validate_against_parent_timestamp(header.header(), parent.header())?;
185
186 self.validate_against_parent_gas_limit(header, parent)?;
189
190 validate_against_parent_eip1559_base_fee(
191 header.header(),
192 parent.header(),
193 &self.chain_spec,
194 )?;
195
196 if self.chain_spec.is_cancun_active_at_timestamp(header.timestamp()) {
198 validate_against_parent_4844(header.header(), parent.header())?;
199 }
200
201 Ok(())
202 }
203
204 fn validate_header_with_total_difficulty(
205 &self,
206 header: &H,
207 total_difficulty: U256,
208 ) -> Result<(), ConsensusError> {
209 let is_post_merge = self
210 .chain_spec
211 .fork(EthereumHardfork::Paris)
212 .active_at_ttd(total_difficulty, header.difficulty());
213
214 if is_post_merge {
215 if !header.difficulty().is_zero() {
217 return Err(ConsensusError::TheMergeDifficultyIsNotZero)
218 }
219
220 if !header.nonce().is_some_and(|nonce| nonce.is_zero()) {
222 return Err(ConsensusError::TheMergeNonceIsNotZero)
223 }
224
225 if header.ommers_hash() != EMPTY_OMMER_ROOT_HASH {
226 return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty)
227 }
228
229 validate_header_extradata(header)?;
239
240 } else {
243 let present_timestamp =
249 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
250
251 if header.timestamp() > present_timestamp + ALLOWED_FUTURE_BLOCK_TIME_SECONDS {
253 return Err(ConsensusError::TimestampIsInFuture {
254 timestamp: header.timestamp(),
255 present_timestamp,
256 })
257 }
258
259 validate_header_extradata(header)?;
260 }
261
262 Ok(())
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use alloy_primitives::B256;
270 use reth_chainspec::{ChainSpec, ChainSpecBuilder};
271 use reth_primitives::proofs;
272
273 fn header_with_gas_limit(gas_limit: u64) -> SealedHeader {
274 let header = reth_primitives::Header { gas_limit, ..Default::default() };
275 SealedHeader::new(header, B256::ZERO)
276 }
277
278 #[test]
279 fn test_valid_gas_limit_increase() {
280 let parent = header_with_gas_limit(GAS_LIMIT_BOUND_DIVISOR * 10);
281 let child = header_with_gas_limit((parent.gas_limit + 5) as u64);
282
283 assert_eq!(
284 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
285 .validate_against_parent_gas_limit(&child, &parent),
286 Ok(())
287 );
288 }
289
290 #[test]
291 fn test_gas_limit_below_minimum() {
292 let parent = header_with_gas_limit(MINIMUM_GAS_LIMIT);
293 let child = header_with_gas_limit(MINIMUM_GAS_LIMIT - 1);
294
295 assert_eq!(
296 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
297 .validate_against_parent_gas_limit(&child, &parent),
298 Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: child.gas_limit as u64 })
299 );
300 }
301
302 #[test]
303 fn test_invalid_gas_limit_increase_exceeding_limit() {
304 let parent = header_with_gas_limit(GAS_LIMIT_BOUND_DIVISOR * 10);
305 let child = header_with_gas_limit(
306 parent.gas_limit + parent.gas_limit / GAS_LIMIT_BOUND_DIVISOR + 1,
307 );
308
309 assert_eq!(
310 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
311 .validate_against_parent_gas_limit(&child, &parent),
312 Err(ConsensusError::GasLimitInvalidIncrease {
313 parent_gas_limit: parent.gas_limit,
314 child_gas_limit: child.gas_limit,
315 })
316 );
317 }
318
319 #[test]
320 fn test_valid_gas_limit_decrease_within_limit() {
321 let parent = header_with_gas_limit(GAS_LIMIT_BOUND_DIVISOR * 10);
322 let child = header_with_gas_limit(parent.gas_limit - 5);
323
324 assert_eq!(
325 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
326 .validate_against_parent_gas_limit(&child, &parent),
327 Ok(())
328 );
329 }
330
331 #[test]
332 fn test_invalid_gas_limit_decrease_exceeding_limit() {
333 let parent = header_with_gas_limit(GAS_LIMIT_BOUND_DIVISOR * 10);
334 let child = header_with_gas_limit(
335 parent.gas_limit - parent.gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1,
336 );
337
338 assert_eq!(
339 EthBeaconConsensus::new(Arc::new(ChainSpec::default()))
340 .validate_against_parent_gas_limit(&child, &parent),
341 Err(ConsensusError::GasLimitInvalidDecrease {
342 parent_gas_limit: parent.gas_limit,
343 child_gas_limit: child.gas_limit,
344 })
345 );
346 }
347
348 #[test]
349 fn shanghai_block_zero_withdrawals() {
350 let chain_spec = Arc::new(ChainSpecBuilder::mainnet().shanghai_activated().build());
353
354 let header = reth_primitives::Header {
355 base_fee_per_gas: Some(1337),
356 withdrawals_root: Some(proofs::calculate_withdrawals_root(&[])),
357 ..Default::default()
358 };
359
360 assert_eq!(
361 EthBeaconConsensus::new(chain_spec).validate_header(&SealedHeader::seal(header,)),
362 Ok(())
363 );
364 }
365}