reth_ethereum_consensus/
validation.rs

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