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    // Check if gas used matches the value set in header.
33    if block.header().gas_used() != result.gas_used {
34        return Err(ConsensusError::BlockGasUsed {
35            gas: GotExpected { got: result.gas_used, expected: block.header().gas_used() },
36            gas_spent_by_tx: gas_spent_by_transactions(&result.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 res = 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(
54                block.header().receipts_root(),
55                block.header().logs_bloom(),
56                &result.receipts,
57            )
58        };
59
60        if let Err(error) = res {
61            let receipts = result
62                .receipts
63                .iter()
64                .map(|r| Bytes::from(r.with_bloom_ref().encoded_2718()))
65                .collect::<Vec<_>>();
66            tracing::debug!(%error, ?receipts, "receipts verification failed");
67            return Err(error)
68        }
69    }
70
71    // Validate that the header requests hash matches the calculated requests hash
72    if chain_spec.is_prague_active_at_timestamp(block.header().timestamp()) {
73        let Some(header_requests_hash) = block.header().requests_hash() else {
74            return Err(ConsensusError::RequestsHashMissing)
75        };
76        let requests_hash = result.requests.requests_hash();
77        if requests_hash != header_requests_hash {
78            return Err(ConsensusError::BodyRequestsHashDiff(
79                GotExpected::new(requests_hash, header_requests_hash).into(),
80            ))
81        }
82    }
83
84    // Validate that the header block access list hash matches the calculated block access list hash
85    if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) &&
86        let Some(block_access_list_hash) = block_access_list_hash
87    {
88        let block_bal_hash = block.header().block_access_list_hash().unwrap_or_default();
89        if block_access_list_hash != block_bal_hash {
90            return Err(ConsensusError::BlockAccessListHashMismatch(
91                GotExpected::new(block_access_list_hash, block_bal_hash).into(),
92            ))
93        }
94    }
95
96    Ok(())
97}
98
99/// Calculate the receipts root, and compare it against the expected receipts root and logs
100/// bloom.
101fn verify_receipts<R: Receipt>(
102    expected_receipts_root: B256,
103    expected_logs_bloom: Bloom,
104    receipts: &[R],
105) -> Result<(), ConsensusError> {
106    // Calculate receipts root.
107    let receipts_with_bloom = receipts.iter().map(TxReceipt::with_bloom_ref).collect::<Vec<_>>();
108    let receipts_root = calculate_receipt_root(&receipts_with_bloom);
109
110    // Calculate header logs bloom.
111    let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom_ref());
112
113    compare_receipts_root_and_logs_bloom(
114        receipts_root,
115        logs_bloom,
116        expected_receipts_root,
117        expected_logs_bloom,
118    )
119}
120
121/// Compare the calculated receipts root with the expected receipts root, also compare
122/// the calculated logs bloom with the expected logs bloom.
123fn compare_receipts_root_and_logs_bloom(
124    calculated_receipts_root: B256,
125    calculated_logs_bloom: Bloom,
126    expected_receipts_root: B256,
127    expected_logs_bloom: Bloom,
128) -> Result<(), ConsensusError> {
129    if calculated_receipts_root != expected_receipts_root {
130        return Err(ConsensusError::BodyReceiptRootDiff(
131            GotExpected { got: calculated_receipts_root, expected: expected_receipts_root }.into(),
132        ))
133    }
134
135    if calculated_logs_bloom != expected_logs_bloom {
136        return Err(ConsensusError::BodyBloomLogDiff(
137            GotExpected { got: calculated_logs_bloom, expected: expected_logs_bloom }.into(),
138        ))
139    }
140
141    Ok(())
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use alloy_primitives::{b256, hex};
148    use reth_ethereum_primitives::Receipt;
149
150    #[test]
151    fn test_verify_receipts_success() {
152        // Create a vector of 5 default Receipt instances
153        let receipts: Vec<Receipt> = vec![Receipt::default(); 5];
154
155        // Compare against expected values
156        assert!(verify_receipts(
157            b256!("0x61353b4fb714dc1fccacbf7eafc4273e62f3d1eed716fe41b2a0cd2e12c63ebc"),
158            Bloom::from(hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
159            &receipts
160        )
161        .is_ok());
162    }
163
164    #[test]
165    fn test_verify_receipts_incorrect_root() {
166        // Generate random expected values to produce a failure
167        let expected_receipts_root = B256::random();
168        let expected_logs_bloom = Bloom::random();
169
170        // Create a vector of 5 random Receipt instances
171        let receipts: Vec<Receipt> = vec![Receipt::default(); 5];
172
173        assert!(verify_receipts(expected_receipts_root, expected_logs_bloom, &receipts).is_err());
174    }
175
176    #[test]
177    fn test_compare_receipts_root_and_logs_bloom_success() {
178        let calculated_receipts_root = B256::random();
179        let calculated_logs_bloom = Bloom::random();
180
181        let expected_receipts_root = calculated_receipts_root;
182        let expected_logs_bloom = calculated_logs_bloom;
183
184        assert!(compare_receipts_root_and_logs_bloom(
185            calculated_receipts_root,
186            calculated_logs_bloom,
187            expected_receipts_root,
188            expected_logs_bloom
189        )
190        .is_ok());
191    }
192
193    #[test]
194    fn test_compare_receipts_root_failure() {
195        let calculated_receipts_root = B256::random();
196        let calculated_logs_bloom = Bloom::random();
197
198        let expected_receipts_root = B256::random();
199        let expected_logs_bloom = calculated_logs_bloom;
200
201        assert!(matches!(
202            compare_receipts_root_and_logs_bloom(
203                calculated_receipts_root,
204                calculated_logs_bloom,
205                expected_receipts_root,
206                expected_logs_bloom
207            ).unwrap_err(),
208            ConsensusError::BodyReceiptRootDiff(diff)
209                if diff.got == calculated_receipts_root && diff.expected == expected_receipts_root
210        ));
211    }
212
213    #[test]
214    fn test_compare_log_bloom_failure() {
215        let calculated_receipts_root = B256::random();
216        let calculated_logs_bloom = Bloom::random();
217
218        let expected_receipts_root = calculated_receipts_root;
219        let expected_logs_bloom = Bloom::random();
220
221        assert!(matches!(
222            compare_receipts_root_and_logs_bloom(
223                calculated_receipts_root,
224                calculated_logs_bloom,
225                expected_receipts_root,
226                expected_logs_bloom
227            ).unwrap_err(),
228            ConsensusError::BodyBloomLogDiff(diff)
229                if diff.got == calculated_logs_bloom && diff.expected == expected_logs_bloom
230        ));
231    }
232}