1use std::{fmt::Debug, sync::Arc};
8
9use alloy_consensus::{TxReceipt, proofs::calculate_receipt_root};
10use alloy_primitives::Bloom;
11use reth_chainspec::{EthChainSpec, EthereumHardforks};
12use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom};
13use reth_execution_types::BlockExecutionResult;
14use reth_primitives_traits::{
15 Block, BlockHeader, GotExpected, NodePrimitives, Receipt, RecoveredBlock, SealedBlock,
16 SealedHeader, receipt::gas_spent_by_transactions,
17};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct ArbConsensus<CS> {
25 chain_spec: Arc<CS>,
26 verify_execution: bool,
27}
28
29impl<CS> ArbConsensus<CS> {
30 pub fn new(chain_spec: Arc<CS>) -> Self {
32 Self {
33 chain_spec,
34 verify_execution: false,
35 }
36 }
37
38 pub fn new_verifying(chain_spec: Arc<CS>) -> Self {
41 Self {
42 chain_spec,
43 verify_execution: true,
44 }
45 }
46}
47
48impl<H, CS> HeaderValidator<H> for ArbConsensus<CS>
49where
50 H: BlockHeader,
51 CS: EthChainSpec<Header = H> + EthereumHardforks + Debug + Send + Sync,
52{
53 fn validate_header(&self, _header: &SealedHeader<H>) -> Result<(), ConsensusError> {
54 Ok(())
55 }
56
57 fn validate_header_against_parent(
58 &self,
59 _header: &SealedHeader<H>,
60 _parent: &SealedHeader<H>,
61 ) -> Result<(), ConsensusError> {
62 Ok(())
63 }
64}
65
66impl<B, CS> Consensus<B> for ArbConsensus<CS>
67where
68 B: Block,
69 CS: EthChainSpec<Header = B::Header> + EthereumHardforks + Debug + Send + Sync,
70{
71 fn validate_body_against_header(
72 &self,
73 _body: &B::Body,
74 _header: &SealedHeader<B::Header>,
75 ) -> Result<(), ConsensusError> {
76 Ok(())
77 }
78
79 fn validate_block_pre_execution(&self, _block: &SealedBlock<B>) -> Result<(), ConsensusError> {
80 Ok(())
81 }
82}
83
84impl<N, CS> FullConsensus<N> for ArbConsensus<CS>
85where
86 N: NodePrimitives,
87 CS: EthChainSpec<Header = N::BlockHeader> + EthereumHardforks + Debug + Send + Sync,
88{
89 fn validate_block_post_execution(
90 &self,
91 block: &RecoveredBlock<N::Block>,
92 result: &BlockExecutionResult<N::Receipt>,
93 receipt_root_bloom: Option<ReceiptRootBloom>,
94 ) -> Result<(), ConsensusError> {
95 if !self.verify_execution {
96 return Ok(());
97 }
98 verify_block_execution(block.header(), &result.receipts, receipt_root_bloom)
99 }
100}
101
102fn verify_block_execution<H, R>(
105 header: &H,
106 receipts: &[R],
107 receipt_root_bloom: Option<ReceiptRootBloom>,
108) -> Result<(), ConsensusError>
109where
110 H: BlockHeader,
111 R: Receipt,
112{
113 let cumulative_gas_used = receipts
114 .last()
115 .map(|r| r.cumulative_gas_used())
116 .unwrap_or(0);
117 if header.gas_used() != cumulative_gas_used {
118 return Err(ConsensusError::BlockGasUsed {
119 gas: GotExpected {
120 got: cumulative_gas_used,
121 expected: header.gas_used(),
122 },
123 gas_spent_by_tx: gas_spent_by_transactions(receipts),
124 });
125 }
126
127 let (receipts_root, logs_bloom) = receipt_root_bloom.unwrap_or_else(|| {
128 let with_bloom = receipts
129 .iter()
130 .map(TxReceipt::with_bloom_ref)
131 .collect::<Vec<_>>();
132 let root = calculate_receipt_root(&with_bloom);
133 let bloom = with_bloom
134 .iter()
135 .fold(Bloom::ZERO, |bloom, r| bloom | r.bloom_ref());
136 (root, bloom)
137 });
138
139 if receipts_root != header.receipts_root() {
140 return Err(ConsensusError::BodyReceiptRootDiff(
141 GotExpected {
142 got: receipts_root,
143 expected: header.receipts_root(),
144 }
145 .into(),
146 ));
147 }
148 if logs_bloom != header.logs_bloom() {
149 return Err(ConsensusError::BodyBloomLogDiff(
150 GotExpected {
151 got: logs_bloom,
152 expected: header.logs_bloom(),
153 }
154 .into(),
155 ));
156 }
157
158 Ok(())
159}
160
161#[cfg(test)]
162mod tests {
163 use alloy_consensus::{
164 Eip658Value, Header, Receipt as AlloyReceipt, TxReceipt, proofs::calculate_receipt_root,
165 };
166 use alloy_primitives::{B256, Bloom};
167 use arb_primitives::{ArbReceipt, ArbReceiptKind};
168 use reth_consensus::ConsensusError;
169
170 use super::verify_block_execution;
171
172 fn receipts() -> Vec<ArbReceipt> {
173 vec![ArbReceipt::new(ArbReceiptKind::Eip1559(AlloyReceipt {
174 status: Eip658Value::Eip658(true),
175 cumulative_gas_used: 21_000,
176 logs: Vec::new(),
177 }))]
178 }
179
180 fn matching_header(receipts: &[ArbReceipt]) -> Header {
181 let with_bloom = receipts
182 .iter()
183 .map(TxReceipt::with_bloom_ref)
184 .collect::<Vec<_>>();
185 Header {
186 gas_used: receipts
187 .last()
188 .map(|r| r.cumulative_gas_used())
189 .unwrap_or(0),
190 receipts_root: calculate_receipt_root(&with_bloom),
191 logs_bloom: with_bloom
192 .iter()
193 .fold(Bloom::ZERO, |b, r| b | r.bloom_ref()),
194 ..Default::default()
195 }
196 }
197
198 #[test]
199 fn accepts_matching_block() {
200 let receipts = receipts();
201 let header = matching_header(&receipts);
202 assert!(verify_block_execution(&header, &receipts, None).is_ok());
203 }
204
205 #[test]
206 fn rejects_gas_mismatch() {
207 let receipts = receipts();
208 let mut header = matching_header(&receipts);
209 header.gas_used += 1;
210 assert!(matches!(
211 verify_block_execution(&header, &receipts, None),
212 Err(ConsensusError::BlockGasUsed { .. })
213 ));
214 }
215
216 #[test]
217 fn rejects_receipts_root_mismatch() {
218 let receipts = receipts();
219 let mut header = matching_header(&receipts);
220 header.receipts_root = B256::ZERO;
221 assert!(matches!(
222 verify_block_execution(&header, &receipts, None),
223 Err(ConsensusError::BodyReceiptRootDiff(_))
224 ));
225 }
226
227 #[test]
228 fn rejects_logs_bloom_mismatch() {
229 let receipts = receipts();
230 let mut header = matching_header(&receipts);
231 header.logs_bloom = Bloom::from([1u8; 256]);
232 assert!(matches!(
233 verify_block_execution(&header, &receipts, None),
234 Err(ConsensusError::BodyBloomLogDiff(_))
235 ));
236 }
237}