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