reth_ethereum_consensus/
validation.rs

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