1use alloy_consensus::{BlockHeader, Transaction};
2use alloy_eips::Encodable2718;
3use op_revm::L1BlockInfo;
4use parking_lot::RwLock;
5use reth_chainspec::ChainSpecProvider;
6use reth_optimism_evm::RethL1BlockInfo;
7use reth_optimism_forks::OpHardforks;
8use reth_primitives_traits::{
9 transaction::error::InvalidTransactionError, Block, BlockBody, GotExpected, SealedBlock,
10};
11use reth_storage_api::{BlockReaderIdExt, StateProviderFactory};
12use reth_transaction_pool::{
13 EthPoolTransaction, EthTransactionValidator, TransactionOrigin, TransactionValidationOutcome,
14 TransactionValidator,
15};
16use std::sync::{
17 atomic::{AtomicU64, Ordering},
18 Arc,
19};
20
21#[derive(Debug, Default)]
23pub struct OpL1BlockInfo {
24 l1_block_info: RwLock<L1BlockInfo>,
26 timestamp: AtomicU64,
28 number: AtomicU64,
30}
31
32#[derive(Debug, Clone)]
34pub struct OpTransactionValidator<Client, Tx> {
35 inner: EthTransactionValidator<Client, Tx>,
37 block_info: Arc<OpL1BlockInfo>,
39 require_l1_data_gas_fee: bool,
43}
44
45impl<Client, Tx> OpTransactionValidator<Client, Tx> {
46 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
48 where
49 Client: ChainSpecProvider,
50 {
51 self.inner.chain_spec()
52 }
53
54 pub fn client(&self) -> &Client {
56 self.inner.client()
57 }
58
59 fn block_timestamp(&self) -> u64 {
61 self.block_info.timestamp.load(Ordering::Relaxed)
62 }
63
64 fn block_number(&self) -> u64 {
66 self.block_info.number.load(Ordering::Relaxed)
67 }
68
69 pub fn require_l1_data_gas_fee(self, require_l1_data_gas_fee: bool) -> Self {
72 Self { require_l1_data_gas_fee, ..self }
73 }
74
75 pub const fn requires_l1_data_gas_fee(&self) -> bool {
78 self.require_l1_data_gas_fee
79 }
80}
81
82impl<Client, Tx> OpTransactionValidator<Client, Tx>
83where
84 Client: ChainSpecProvider<ChainSpec: OpHardforks> + StateProviderFactory + BlockReaderIdExt,
85 Tx: EthPoolTransaction,
86{
87 pub fn new(inner: EthTransactionValidator<Client, Tx>) -> Self {
89 let this = Self::with_block_info(inner, OpL1BlockInfo::default());
90 if let Ok(Some(block)) =
91 this.inner.client().block_by_number_or_tag(alloy_eips::BlockNumberOrTag::Latest)
92 {
93 if block.header().number() == 0 {
96 this.block_info.timestamp.store(block.header().timestamp(), Ordering::Relaxed);
97 this.block_info.number.store(block.header().number(), Ordering::Relaxed);
98 } else {
99 this.update_l1_block_info(block.header(), block.body().transactions().first());
100 }
101 }
102
103 this
104 }
105
106 pub fn with_block_info(
108 inner: EthTransactionValidator<Client, Tx>,
109 block_info: OpL1BlockInfo,
110 ) -> Self {
111 Self { inner, block_info: Arc::new(block_info), require_l1_data_gas_fee: true }
112 }
113
114 pub fn update_l1_block_info<H, T>(&self, header: &H, tx: Option<&T>)
118 where
119 H: BlockHeader,
120 T: Transaction,
121 {
122 self.block_info.timestamp.store(header.timestamp(), Ordering::Relaxed);
123 self.block_info.number.store(header.number(), Ordering::Relaxed);
124
125 if let Some(Ok(cost_addition)) = tx.map(reth_optimism_evm::extract_l1_info_from_tx) {
126 *self.block_info.l1_block_info.write() = cost_addition;
127 }
128 }
129
130 pub fn validate_one(
137 &self,
138 origin: TransactionOrigin,
139 transaction: Tx,
140 ) -> TransactionValidationOutcome<Tx> {
141 if transaction.is_eip4844() {
142 return TransactionValidationOutcome::Invalid(
143 transaction,
144 InvalidTransactionError::TxTypeNotSupported.into(),
145 )
146 }
147
148 let outcome = self.inner.validate_one(origin, transaction);
149
150 if !self.requires_l1_data_gas_fee() {
151 return outcome
153 }
154
155 if let TransactionValidationOutcome::Valid {
157 balance,
158 state_nonce,
159 transaction: valid_tx,
160 propagate,
161 } = outcome
162 {
163 let mut l1_block_info = self.block_info.l1_block_info.read().clone();
164
165 let mut encoded = Vec::with_capacity(valid_tx.transaction().encoded_length());
166 let tx = valid_tx.transaction().clone_into_consensus();
167 tx.encode_2718(&mut encoded);
168
169 let cost_addition = match l1_block_info.l1_tx_data_fee(
170 self.chain_spec(),
171 self.block_timestamp(),
172 self.block_number(),
173 &encoded,
174 false,
175 ) {
176 Ok(cost) => cost,
177 Err(err) => {
178 return TransactionValidationOutcome::Error(*valid_tx.hash(), Box::new(err))
179 }
180 };
181 let cost = valid_tx.transaction().cost().saturating_add(cost_addition);
182
183 if cost > balance {
185 return TransactionValidationOutcome::Invalid(
186 valid_tx.into_transaction(),
187 InvalidTransactionError::InsufficientFunds(
188 GotExpected { got: balance, expected: cost }.into(),
189 )
190 .into(),
191 )
192 }
193
194 return TransactionValidationOutcome::Valid {
195 balance,
196 state_nonce,
197 transaction: valid_tx,
198 propagate,
199 }
200 }
201
202 outcome
203 }
204
205 pub fn validate_all(
211 &self,
212 transactions: Vec<(TransactionOrigin, Tx)>,
213 ) -> Vec<TransactionValidationOutcome<Tx>> {
214 transactions.into_iter().map(|(origin, tx)| self.validate_one(origin, tx)).collect()
215 }
216}
217
218impl<Client, Tx> TransactionValidator for OpTransactionValidator<Client, Tx>
219where
220 Client: ChainSpecProvider<ChainSpec: OpHardforks> + StateProviderFactory + BlockReaderIdExt,
221 Tx: EthPoolTransaction,
222{
223 type Transaction = Tx;
224
225 async fn validate_transaction(
226 &self,
227 origin: TransactionOrigin,
228 transaction: Self::Transaction,
229 ) -> TransactionValidationOutcome<Self::Transaction> {
230 self.validate_one(origin, transaction)
231 }
232
233 async fn validate_transactions(
234 &self,
235 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
236 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
237 self.validate_all(transactions)
238 }
239
240 fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
241 where
242 B: Block,
243 {
244 self.inner.on_new_head_block(new_tip_block);
245 self.update_l1_block_info(
246 new_tip_block.header(),
247 new_tip_block.body().transactions().first(),
248 );
249 }
250}