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