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