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
180    use super::*;
181    use alloy_primitives::b256;
182    use reth_ethereum_primitives::Receipt;
183
184    #[test]
185    fn test_verify_receipts_success() {
186        // Create a vector of 5 default Receipt instances
187        let receipts: Vec<Receipt> = vec![Receipt::default(); 5];
188
189        // Compare against expected values
190        assert!(verify_receipts(
191            b256!("0x61353b4fb714dc1fccacbf7eafc4273e62f3d1eed716fe41b2a0cd2e12c63ebc"),
192            Bloom::ZERO,
193            &receipts
194        )
195        .is_ok());
196    }
197
198    #[test]
199    fn test_verify_receipts_incorrect_root() {
200        // Generate random expected values to produce a failure
201        let expected_receipts_root = B256::random();
202        let expected_logs_bloom = Bloom::random();
203
204        // Create a vector of 5 random Receipt instances
205        let receipts: Vec<Receipt> = vec![Receipt::default(); 5];
206
207        assert!(verify_receipts(expected_receipts_root, expected_logs_bloom, &receipts).is_err());
208    }
209
210    #[test]
211    fn test_compare_receipts_root_and_logs_bloom_success() {
212        let calculated_receipts_root = B256::random();
213        let calculated_logs_bloom = Bloom::random();
214
215        let expected_receipts_root = calculated_receipts_root;
216        let expected_logs_bloom = calculated_logs_bloom;
217
218        assert!(compare_receipts_root_and_logs_bloom(
219            calculated_receipts_root,
220            calculated_logs_bloom,
221            expected_receipts_root,
222            expected_logs_bloom
223        )
224        .is_ok());
225    }
226
227    #[test]
228    fn test_compare_receipts_root_failure() {
229        let calculated_receipts_root = B256::random();
230        let calculated_logs_bloom = Bloom::random();
231
232        let expected_receipts_root = B256::random();
233        let expected_logs_bloom = calculated_logs_bloom;
234
235        assert!(matches!(
236            compare_receipts_root_and_logs_bloom(
237                calculated_receipts_root,
238                calculated_logs_bloom,
239                expected_receipts_root,
240                expected_logs_bloom
241            ).unwrap_err(),
242            ConsensusError::BodyReceiptRootDiff(diff)
243                if diff.got == calculated_receipts_root && diff.expected == expected_receipts_root
244        ));
245    }
246
247    #[test]
248    fn test_compare_log_bloom_failure() {
249        let calculated_receipts_root = B256::random();
250        let calculated_logs_bloom = Bloom::random();
251
252        let expected_receipts_root = calculated_receipts_root;
253        let expected_logs_bloom = Bloom::random();
254
255        assert!(matches!(
256            compare_receipts_root_and_logs_bloom(
257                calculated_receipts_root,
258                calculated_logs_bloom,
259                expected_receipts_root,
260                expected_logs_bloom
261            ).unwrap_err(),
262            ConsensusError::BodyBloomLogDiff(diff)
263                if diff.got == calculated_logs_bloom && diff.expected == expected_logs_bloom
264        ));
265    }
266}