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