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    let is_amsterdam = chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp());
114    if is_amsterdam && block_access_list_hash.is_none() {
115        return Err(ConsensusError::BlockAccessListHashMissing)
116    }
117
118    if (is_amsterdam || is_allowed_pre_amsterdam_bal_hash) &&
119        let Some(block_access_list_hash) = block_access_list_hash
120    {
121        let block_bal_hash = block.header().block_access_list_hash().unwrap_or_default();
122        if block_access_list_hash != block_bal_hash {
123            return Err(ConsensusError::BlockAccessListHashMismatch(
124                GotExpected::new(block_access_list_hash, block_bal_hash).into(),
125            ))
126        }
127    }
128
129    Ok(())
130}
131
132/// Calculate the receipts root, and compare it against the expected receipts root and logs
133/// bloom.
134pub fn verify_receipts<R: Receipt>(
135    expected_receipts_root: B256,
136    expected_logs_bloom: Bloom,
137    receipts: &[R],
138) -> Result<(), ConsensusError> {
139    // Calculate receipts root.
140    let receipts_with_bloom = receipts.iter().map(TxReceipt::with_bloom_ref).collect::<Vec<_>>();
141    let receipts_root = calculate_receipt_root(&receipts_with_bloom);
142
143    // Calculate header logs bloom.
144    let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom_ref());
145
146    compare_receipts_root_and_logs_bloom(
147        receipts_root,
148        logs_bloom,
149        expected_receipts_root,
150        expected_logs_bloom,
151    )
152}
153
154/// Compare the calculated receipts root with the expected receipts root, also compare
155/// the calculated logs bloom with the expected logs bloom.
156pub fn compare_receipts_root_and_logs_bloom(
157    calculated_receipts_root: B256,
158    calculated_logs_bloom: Bloom,
159    expected_receipts_root: B256,
160    expected_logs_bloom: Bloom,
161) -> Result<(), ConsensusError> {
162    if calculated_receipts_root != expected_receipts_root {
163        return Err(ConsensusError::BodyReceiptRootDiff(
164            GotExpected { got: calculated_receipts_root, expected: expected_receipts_root }.into(),
165        ))
166    }
167
168    if calculated_logs_bloom != expected_logs_bloom {
169        return Err(ConsensusError::BodyBloomLogDiff(
170            GotExpected { got: calculated_logs_bloom, expected: expected_logs_bloom }.into(),
171        ))
172    }
173
174    Ok(())
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    use alloy_primitives::{b256, hex};
181    use reth_ethereum_primitives::Receipt;
182
183    #[test]
184    fn test_verify_receipts_success() {
185        // Create a vector of 5 default Receipt instances
186        let receipts: Vec<Receipt> = vec![Receipt::default(); 5];
187
188        // Compare against expected values
189        assert!(verify_receipts(
190            b256!("0x61353b4fb714dc1fccacbf7eafc4273e62f3d1eed716fe41b2a0cd2e12c63ebc"),
191            Bloom::from(hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
192            &receipts
193        )
194        .is_ok());
195    }
196
197    #[test]
198    fn test_verify_receipts_incorrect_root() {
199        // Generate random expected values to produce a failure
200        let expected_receipts_root = B256::random();
201        let expected_logs_bloom = Bloom::random();
202
203        // Create a vector of 5 random Receipt instances
204        let receipts: Vec<Receipt> = vec![Receipt::default(); 5];
205
206        assert!(verify_receipts(expected_receipts_root, expected_logs_bloom, &receipts).is_err());
207    }
208
209    #[test]
210    fn test_compare_receipts_root_and_logs_bloom_success() {
211        let calculated_receipts_root = B256::random();
212        let calculated_logs_bloom = Bloom::random();
213
214        let expected_receipts_root = calculated_receipts_root;
215        let expected_logs_bloom = calculated_logs_bloom;
216
217        assert!(compare_receipts_root_and_logs_bloom(
218            calculated_receipts_root,
219            calculated_logs_bloom,
220            expected_receipts_root,
221            expected_logs_bloom
222        )
223        .is_ok());
224    }
225
226    #[test]
227    fn test_compare_receipts_root_failure() {
228        let calculated_receipts_root = B256::random();
229        let calculated_logs_bloom = Bloom::random();
230
231        let expected_receipts_root = B256::random();
232        let expected_logs_bloom = calculated_logs_bloom;
233
234        assert!(matches!(
235            compare_receipts_root_and_logs_bloom(
236                calculated_receipts_root,
237                calculated_logs_bloom,
238                expected_receipts_root,
239                expected_logs_bloom
240            ).unwrap_err(),
241            ConsensusError::BodyReceiptRootDiff(diff)
242                if diff.got == calculated_receipts_root && diff.expected == expected_receipts_root
243        ));
244    }
245
246    #[test]
247    fn test_compare_log_bloom_failure() {
248        let calculated_receipts_root = B256::random();
249        let calculated_logs_bloom = Bloom::random();
250
251        let expected_receipts_root = calculated_receipts_root;
252        let expected_logs_bloom = Bloom::random();
253
254        assert!(matches!(
255            compare_receipts_root_and_logs_bloom(
256                calculated_receipts_root,
257                calculated_logs_bloom,
258                expected_receipts_root,
259                expected_logs_bloom
260            ).unwrap_err(),
261            ConsensusError::BodyBloomLogDiff(diff)
262                if diff.got == calculated_logs_bloom && diff.expected == expected_logs_bloom
263        ));
264    }
265}