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_traits::{
7    receipt::gas_spent_by_transactions, Block, GotExpected, Receipt, RecoveredBlock,
8};
9
10/// Validate a block with regard to execution results:
11///
12/// - Compares the receipts root in the block header to the block body
13/// - Compares the gas used in the block header to the actual gas usage after execution
14pub fn validate_block_post_execution<B, R, ChainSpec>(
15    block: &RecoveredBlock<B>,
16    chain_spec: &ChainSpec,
17    receipts: &[R],
18    requests: &Requests,
19) -> Result<(), ConsensusError>
20where
21    B: Block,
22    R: Receipt,
23    ChainSpec: EthereumHardforks,
24{
25    // Check if gas used matches the value set in header.
26    let cumulative_gas_used =
27        receipts.last().map(|receipt| receipt.cumulative_gas_used()).unwrap_or(0);
28    if block.header().gas_used() != cumulative_gas_used {
29        return Err(ConsensusError::BlockGasUsed {
30            gas: GotExpected { got: cumulative_gas_used, expected: block.header().gas_used() },
31            gas_spent_by_tx: gas_spent_by_transactions(receipts),
32        })
33    }
34
35    // Before Byzantium, receipts contained state root that would mean that expensive
36    // operation as hashing that is required for state root got calculated in every
37    // transaction This was replaced with is_success flag.
38    // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658
39    if chain_spec.is_byzantium_active_at_block(block.header().number()) {
40        if let Err(error) =
41            verify_receipts(block.header().receipts_root(), block.header().logs_bloom(), receipts)
42        {
43            tracing::debug!(%error, ?receipts, "receipts verification failed");
44            return Err(error)
45        }
46    }
47
48    // Validate that the header requests hash matches the calculated requests hash
49    if chain_spec.is_prague_active_at_timestamp(block.header().timestamp()) {
50        let Some(header_requests_hash) = block.header().requests_hash() else {
51            return Err(ConsensusError::RequestsHashMissing)
52        };
53        let requests_hash = requests.requests_hash();
54        if requests_hash != header_requests_hash {
55            return Err(ConsensusError::BodyRequestsHashDiff(
56                GotExpected::new(requests_hash, header_requests_hash).into(),
57            ))
58        }
59    }
60
61    Ok(())
62}
63
64/// Calculate the receipts root, and compare it against against the expected receipts root and logs
65/// bloom.
66fn verify_receipts<R: Receipt>(
67    expected_receipts_root: B256,
68    expected_logs_bloom: Bloom,
69    receipts: &[R],
70) -> Result<(), ConsensusError> {
71    // Calculate receipts root.
72    let receipts_with_bloom = receipts.iter().map(TxReceipt::with_bloom_ref).collect::<Vec<_>>();
73    let receipts_root = calculate_receipt_root(&receipts_with_bloom);
74
75    // Calculate header logs bloom.
76    let logs_bloom = receipts_with_bloom.iter().fold(Bloom::ZERO, |bloom, r| bloom | r.bloom());
77
78    compare_receipts_root_and_logs_bloom(
79        receipts_root,
80        logs_bloom,
81        expected_receipts_root,
82        expected_logs_bloom,
83    )?;
84
85    Ok(())
86}
87
88/// Compare the calculated receipts root with the expected receipts root, also compare
89/// the calculated logs bloom with the expected logs bloom.
90fn compare_receipts_root_and_logs_bloom(
91    calculated_receipts_root: B256,
92    calculated_logs_bloom: Bloom,
93    expected_receipts_root: B256,
94    expected_logs_bloom: Bloom,
95) -> Result<(), ConsensusError> {
96    if calculated_receipts_root != expected_receipts_root {
97        return Err(ConsensusError::BodyReceiptRootDiff(
98            GotExpected { got: calculated_receipts_root, expected: expected_receipts_root }.into(),
99        ))
100    }
101
102    if calculated_logs_bloom != expected_logs_bloom {
103        return Err(ConsensusError::BodyBloomLogDiff(
104            GotExpected { got: calculated_logs_bloom, expected: expected_logs_bloom }.into(),
105        ))
106    }
107
108    Ok(())
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use alloy_primitives::{b256, hex};
115    use reth_ethereum_primitives::Receipt;
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!("0x61353b4fb714dc1fccacbf7eafc4273e62f3d1eed716fe41b2a0cd2e12c63ebc"),
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}