reth_ethereum_consensus/
validation.rs

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