Skip to main content

reth_ethereum_consensus/
validation.rs

1use alloc::vec::Vec;
2use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
3use alloy_eips::Encodable2718;
4use alloy_primitives::{Bloom, Bytes, B256};
5use reth_chainspec::EthereumHardforks;
6use reth_consensus::ConsensusError;
7use reth_execution_types::BlockExecutionResult;
8use reth_primitives_traits::{
9    receipt::gas_spent_by_transactions, Block, GotExpected, Receipt, RecoveredBlock,
10};
11
12/// Validate a block with regard to execution results:
13///
14/// - Compares the receipts root in the block header to the block body
15/// - Compares the gas used in the block header to the actual gas usage after execution
16/// - Compares the computed Block Access List Hash to the value in the header if Amsterdam is active
17///
18/// If `receipt_root_bloom` is provided, the pre-computed receipt root and logs bloom are used
19/// instead of computing them from the receipts.
20pub fn validate_block_post_execution<B, R, ChainSpec>(
21    block: &RecoveredBlock<B>,
22    chain_spec: &ChainSpec,
23    result: &BlockExecutionResult<R>,
24    receipt_root_bloom: Option<(B256, Bloom)>,
25    block_access_list_hash: Option<B256>,
26) -> Result<(), ConsensusError>
27where
28    B: Block,
29    R: Receipt,
30    ChainSpec: EthereumHardforks,
31{
32    validate_block_post_execution_with_bal_hashes(
33        block,
34        chain_spec,
35        result,
36        receipt_root_bloom,
37        block_access_list_hash,
38        false,
39    )
40}
41
42/// Validate a block with regard to execution results, optionally allowing pre-Amsterdam BAL hashes.
43pub(crate) fn validate_block_post_execution_with_bal_hashes<B, R, ChainSpec>(
44    block: &RecoveredBlock<B>,
45    chain_spec: &ChainSpec,
46    result: &BlockExecutionResult<R>,
47    receipt_root_bloom: Option<(B256, Bloom)>,
48    block_access_list_hash: Option<B256>,
49    allow_bal_hashes: bool,
50) -> Result<(), ConsensusError>
51where
52    B: Block,
53    R: Receipt,
54    ChainSpec: EthereumHardforks,
55{
56    // Check if gas used matches the value set in header.
57    if block.header().gas_used() != result.gas_used {
58        return Err(ConsensusError::BlockGasUsed {
59            gas: GotExpected { got: result.gas_used, expected: block.header().gas_used() },
60            gas_spent_by_tx: gas_spent_by_transactions(&result.receipts),
61        })
62    }
63
64    // Before Byzantium, receipts contained state root that would mean that expensive
65    // operation as hashing that is required for state root got calculated in every
66    // transaction This was replaced with is_success flag.
67    // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658
68    if chain_spec.is_byzantium_active_at_block(block.header().number()) {
69        let res = if let Some((receipts_root, logs_bloom)) = receipt_root_bloom {
70            compare_receipts_root_and_logs_bloom(
71                receipts_root,
72                logs_bloom,
73                block.header().receipts_root(),
74                block.header().logs_bloom(),
75            )
76        } else {
77            verify_receipts(
78                block.header().receipts_root(),
79                block.header().logs_bloom(),
80                &result.receipts,
81            )
82        };
83
84        if let Err(error) = res {
85            let receipts = result
86                .receipts
87                .iter()
88                .map(|r| Bytes::from(r.with_bloom_ref().encoded_2718()))
89                .collect::<Vec<_>>();
90            tracing::debug!(%error, ?receipts, "receipts verification failed");
91            return Err(error)
92        }
93    }
94
95    // Validate that the header requests hash matches the calculated requests hash
96    if chain_spec.is_prague_active_at_timestamp(block.header().timestamp()) {
97        let Some(header_requests_hash) = block.header().requests_hash() else {
98            return Err(ConsensusError::RequestsHashMissing)
99        };
100        let requests_hash = result.requests.requests_hash();
101        if requests_hash != header_requests_hash {
102            return Err(ConsensusError::BodyRequestsHashDiff(
103                GotExpected::new(requests_hash, header_requests_hash).into(),
104            ))
105        }
106    }
107
108    // Validate that the header block access list hash matches the calculated block access list hash
109    let is_allowed_pre_amsterdam_bal_hash = allow_bal_hashes &&
110        !chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) &&
111        block.header().block_access_list_hash().is_some();
112
113    if (chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) ||
114        is_allowed_pre_amsterdam_bal_hash) &&
115        let Some(block_access_list_hash) = block_access_list_hash
116    {
117        let block_bal_hash = block.header().block_access_list_hash().unwrap_or_default();
118        if block_access_list_hash != block_bal_hash {
119            return Err(ConsensusError::BlockAccessListHashMismatch(
120                GotExpected::new(block_access_list_hash, block_bal_hash).into(),
121            ))
122        }
123    }
124
125    Ok(())
126}
127
128/// Calculate the receipts root, and compare it against the expected receipts root and logs
129/// bloom.
130fn verify_receipts<R: Receipt>(
131    expected_receipts_root: B256,
132    expected_logs_bloom: Bloom,
133    receipts: &[R],
134) -> Result<(), ConsensusError> {
135    // Calculate receipts root.
136    let receipts_with_bloom = receipts.iter().map(TxReceipt::with_bloom_ref).collect::<Vec<_>>();
137    let receipts_root = calculate_receipt_root(&receipts_with_bloom);
138
139    // Calculate header logs bloom.
140    let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom_ref());
141
142    compare_receipts_root_and_logs_bloom(
143        receipts_root,
144        logs_bloom,
145        expected_receipts_root,
146        expected_logs_bloom,
147    )
148}
149
150/// Compare the calculated receipts root with the expected receipts root, also compare
151/// the calculated logs bloom with the expected logs bloom.
152fn compare_receipts_root_and_logs_bloom(
153    calculated_receipts_root: B256,
154    calculated_logs_bloom: Bloom,
155    expected_receipts_root: B256,
156    expected_logs_bloom: Bloom,
157) -> Result<(), ConsensusError> {
158    if calculated_receipts_root != expected_receipts_root {
159        return Err(ConsensusError::BodyReceiptRootDiff(
160            GotExpected { got: calculated_receipts_root, expected: expected_receipts_root }.into(),
161        ))
162    }
163
164    if calculated_logs_bloom != expected_logs_bloom {
165        return Err(ConsensusError::BodyBloomLogDiff(
166            GotExpected { got: calculated_logs_bloom, expected: expected_logs_bloom }.into(),
167        ))
168    }
169
170    Ok(())
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use alloy_primitives::{b256, hex};
177    use reth_ethereum_primitives::Receipt;
178
179    #[test]
180    fn test_verify_receipts_success() {
181        // Create a vector of 5 default Receipt instances
182        let receipts: Vec<Receipt> = vec![Receipt::default(); 5];
183
184        // Compare against expected values
185        assert!(verify_receipts(
186            b256!("0x61353b4fb714dc1fccacbf7eafc4273e62f3d1eed716fe41b2a0cd2e12c63ebc"),
187            Bloom::from(hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
188            &receipts
189        )
190        .is_ok());
191    }
192
193    #[test]
194    fn test_verify_receipts_incorrect_root() {
195        // Generate random expected values to produce a failure
196        let expected_receipts_root = B256::random();
197        let expected_logs_bloom = Bloom::random();
198
199        // Create a vector of 5 random Receipt instances
200        let receipts: Vec<Receipt> = vec![Receipt::default(); 5];
201
202        assert!(verify_receipts(expected_receipts_root, expected_logs_bloom, &receipts).is_err());
203    }
204
205    #[test]
206    fn test_compare_receipts_root_and_logs_bloom_success() {
207        let calculated_receipts_root = B256::random();
208        let calculated_logs_bloom = Bloom::random();
209
210        let expected_receipts_root = calculated_receipts_root;
211        let expected_logs_bloom = calculated_logs_bloom;
212
213        assert!(compare_receipts_root_and_logs_bloom(
214            calculated_receipts_root,
215            calculated_logs_bloom,
216            expected_receipts_root,
217            expected_logs_bloom
218        )
219        .is_ok());
220    }
221
222    #[test]
223    fn test_compare_receipts_root_failure() {
224        let calculated_receipts_root = B256::random();
225        let calculated_logs_bloom = Bloom::random();
226
227        let expected_receipts_root = B256::random();
228        let expected_logs_bloom = calculated_logs_bloom;
229
230        assert!(matches!(
231            compare_receipts_root_and_logs_bloom(
232                calculated_receipts_root,
233                calculated_logs_bloom,
234                expected_receipts_root,
235                expected_logs_bloom
236            ).unwrap_err(),
237            ConsensusError::BodyReceiptRootDiff(diff)
238                if diff.got == calculated_receipts_root && diff.expected == expected_receipts_root
239        ));
240    }
241
242    #[test]
243    fn test_compare_log_bloom_failure() {
244        let calculated_receipts_root = B256::random();
245        let calculated_logs_bloom = Bloom::random();
246
247        let expected_receipts_root = calculated_receipts_root;
248        let expected_logs_bloom = Bloom::random();
249
250        assert!(matches!(
251            compare_receipts_root_and_logs_bloom(
252                calculated_receipts_root,
253                calculated_logs_bloom,
254                expected_receipts_root,
255                expected_logs_bloom
256            ).unwrap_err(),
257            ConsensusError::BodyBloomLogDiff(diff)
258                if diff.got == calculated_logs_bloom && diff.expected == expected_logs_bloom
259        ));
260    }
261}