1use crate::{interop::MaybeInteropTransaction, supervisor::SupervisorClient, InvalidCrossTx};
2use alloy_consensus::{BlockHeader, Transaction};
3use alloy_eips::Encodable2718;
4use op_revm::L1BlockInfo;
5use parking_lot::RwLock;
6use reth_chainspec::ChainSpecProvider;
7use reth_optimism_evm::RethL1BlockInfo;
8use reth_optimism_forks::OpHardforks;
9use reth_primitives_traits::{
10 transaction::error::InvalidTransactionError, Block, BlockBody, GotExpected, SealedBlock,
11};
12use reth_storage_api::{BlockReaderIdExt, StateProvider, StateProviderFactory};
13use reth_transaction_pool::{
14 error::InvalidPoolTransactionError, EthPoolTransaction, EthTransactionValidator,
15 TransactionOrigin, TransactionValidationOutcome, TransactionValidator,
16};
17use std::sync::{
18 atomic::{AtomicBool, AtomicU64, Ordering},
19 Arc,
20};
21
22const TRANSACTION_VALIDITY_WINDOW_SECS: u64 = 3600;
24
25#[derive(Debug, Default)]
27pub struct OpL1BlockInfo {
28 l1_block_info: RwLock<L1BlockInfo>,
30 timestamp: AtomicU64,
32 number: AtomicU64,
34}
35
36impl OpL1BlockInfo {
37 pub fn timestamp(&self) -> u64 {
39 self.timestamp.load(Ordering::Relaxed)
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct OpTransactionValidator<Client, Tx> {
46 inner: EthTransactionValidator<Client, Tx>,
48 block_info: Arc<OpL1BlockInfo>,
50 require_l1_data_gas_fee: bool,
54 supervisor_client: Option<SupervisorClient>,
56 fork_tracker: Arc<OpForkTracker>,
58}
59
60impl<Client, Tx> OpTransactionValidator<Client, Tx> {
61 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
63 where
64 Client: ChainSpecProvider,
65 {
66 self.inner.chain_spec()
67 }
68
69 pub fn client(&self) -> &Client {
71 self.inner.client()
72 }
73
74 fn block_timestamp(&self) -> u64 {
76 self.block_info.timestamp.load(Ordering::Relaxed)
77 }
78
79 pub fn require_l1_data_gas_fee(self, require_l1_data_gas_fee: bool) -> Self {
82 Self { require_l1_data_gas_fee, ..self }
83 }
84
85 pub const fn requires_l1_data_gas_fee(&self) -> bool {
88 self.require_l1_data_gas_fee
89 }
90}
91
92impl<Client, Tx> OpTransactionValidator<Client, Tx>
93where
94 Client: ChainSpecProvider<ChainSpec: OpHardforks> + StateProviderFactory + BlockReaderIdExt,
95 Tx: EthPoolTransaction + MaybeInteropTransaction,
96{
97 pub fn new(inner: EthTransactionValidator<Client, Tx>) -> Self {
99 let this = Self::with_block_info(inner, OpL1BlockInfo::default());
100 if let Ok(Some(block)) =
101 this.inner.client().block_by_number_or_tag(alloy_eips::BlockNumberOrTag::Latest)
102 {
103 if block.header().number() == 0 {
106 this.block_info.timestamp.store(block.header().timestamp(), Ordering::Relaxed);
107 this.block_info.number.store(block.header().number(), Ordering::Relaxed);
108 } else {
109 this.update_l1_block_info(block.header(), block.body().transactions().first());
110 }
111 }
112
113 this
114 }
115
116 pub fn with_block_info(
118 inner: EthTransactionValidator<Client, Tx>,
119 block_info: OpL1BlockInfo,
120 ) -> Self {
121 Self {
122 inner,
123 block_info: Arc::new(block_info),
124 require_l1_data_gas_fee: true,
125 supervisor_client: None,
126 fork_tracker: Arc::new(OpForkTracker { interop: AtomicBool::from(false) }),
127 }
128 }
129
130 pub fn with_supervisor(mut self, supervisor_client: SupervisorClient) -> Self {
132 self.supervisor_client = Some(supervisor_client);
133 self
134 }
135
136 pub fn update_l1_block_info<H, T>(&self, header: &H, tx: Option<&T>)
140 where
141 H: BlockHeader,
142 T: Transaction,
143 {
144 self.block_info.timestamp.store(header.timestamp(), Ordering::Relaxed);
145 self.block_info.number.store(header.number(), Ordering::Relaxed);
146
147 if let Some(Ok(cost_addition)) = tx.map(reth_optimism_evm::extract_l1_info_from_tx) {
148 *self.block_info.l1_block_info.write() = cost_addition;
149 }
150
151 if self.chain_spec().is_interop_active_at_timestamp(header.timestamp()) {
152 self.fork_tracker.interop.store(true, Ordering::Relaxed);
153 }
154 }
155
156 pub async fn validate_one(
163 &self,
164 origin: TransactionOrigin,
165 transaction: Tx,
166 ) -> TransactionValidationOutcome<Tx> {
167 self.validate_one_with_state(origin, transaction, &mut None).await
168 }
169
170 pub async fn validate_one_with_state(
182 &self,
183 origin: TransactionOrigin,
184 transaction: Tx,
185 state: &mut Option<Box<dyn StateProvider>>,
186 ) -> TransactionValidationOutcome<Tx> {
187 if transaction.is_eip4844() {
188 return TransactionValidationOutcome::Invalid(
189 transaction,
190 InvalidTransactionError::TxTypeNotSupported.into(),
191 )
192 }
193
194 match self.is_valid_cross_tx(&transaction).await {
196 Some(Err(err)) => {
197 let err = match err {
198 InvalidCrossTx::CrossChainTxPreInterop => {
199 InvalidTransactionError::TxTypeNotSupported.into()
200 }
201 err => InvalidPoolTransactionError::Other(Box::new(err)),
202 };
203 return TransactionValidationOutcome::Invalid(transaction, err)
204 }
205 Some(Ok(_)) => {
206 transaction.set_interop_deadline(
208 self.block_timestamp() + TRANSACTION_VALIDITY_WINDOW_SECS,
209 );
210 }
211 _ => {}
212 }
213
214 let outcome = self.inner.validate_one_with_state(origin, transaction, state);
215
216 self.apply_op_checks(outcome)
217 }
218
219 pub async fn validate_all(
225 &self,
226 transactions: Vec<(TransactionOrigin, Tx)>,
227 ) -> Vec<TransactionValidationOutcome<Tx>> {
228 futures_util::future::join_all(
229 transactions.into_iter().map(|(origin, tx)| self.validate_one(origin, tx)),
230 )
231 .await
232 }
233
234 fn apply_op_checks(
236 &self,
237 outcome: TransactionValidationOutcome<Tx>,
238 ) -> TransactionValidationOutcome<Tx> {
239 if !self.requires_l1_data_gas_fee() {
240 return outcome
242 }
243 if let TransactionValidationOutcome::Valid {
245 balance,
246 state_nonce,
247 transaction: valid_tx,
248 propagate,
249 } = outcome
250 {
251 let mut l1_block_info = self.block_info.l1_block_info.read().clone();
252
253 let mut encoded = Vec::with_capacity(valid_tx.transaction().encoded_length());
254 let tx = valid_tx.transaction().clone_into_consensus();
255 tx.encode_2718(&mut encoded);
256
257 let cost_addition = match l1_block_info.l1_tx_data_fee(
258 self.chain_spec(),
259 self.block_timestamp(),
260 &encoded,
261 false,
262 ) {
263 Ok(cost) => cost,
264 Err(err) => {
265 return TransactionValidationOutcome::Error(*valid_tx.hash(), Box::new(err))
266 }
267 };
268 let cost = valid_tx.transaction().cost().saturating_add(cost_addition);
269
270 if cost > balance {
272 return TransactionValidationOutcome::Invalid(
273 valid_tx.into_transaction(),
274 InvalidTransactionError::InsufficientFunds(
275 GotExpected { got: balance, expected: cost }.into(),
276 )
277 .into(),
278 )
279 }
280
281 return TransactionValidationOutcome::Valid {
282 balance,
283 state_nonce,
284 transaction: valid_tx,
285 propagate,
286 }
287 }
288 outcome
289 }
290
291 pub async fn is_valid_cross_tx(&self, tx: &Tx) -> Option<Result<(), InvalidCrossTx>> {
293 self.supervisor_client
296 .as_ref()?
297 .is_valid_cross_tx(
298 tx.access_list(),
299 tx.hash(),
300 self.block_info.timestamp.load(Ordering::Relaxed),
301 Some(TRANSACTION_VALIDITY_WINDOW_SECS),
302 self.fork_tracker.is_interop_activated(),
303 )
304 .await
305 }
306}
307
308impl<Client, Tx> TransactionValidator for OpTransactionValidator<Client, Tx>
309where
310 Client: ChainSpecProvider<ChainSpec: OpHardforks> + StateProviderFactory + BlockReaderIdExt,
311 Tx: EthPoolTransaction + MaybeInteropTransaction,
312{
313 type Transaction = Tx;
314
315 async fn validate_transaction(
316 &self,
317 origin: TransactionOrigin,
318 transaction: Self::Transaction,
319 ) -> TransactionValidationOutcome<Self::Transaction> {
320 self.validate_one(origin, transaction).await
321 }
322
323 async fn validate_transactions(
324 &self,
325 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
326 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
327 self.validate_all(transactions).await
328 }
329
330 fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
331 where
332 B: Block,
333 {
334 self.inner.on_new_head_block(new_tip_block);
335 self.update_l1_block_info(
336 new_tip_block.header(),
337 new_tip_block.body().transactions().first(),
338 );
339 }
340}
341
342#[derive(Debug)]
344pub(crate) struct OpForkTracker {
345 interop: AtomicBool,
347}
348
349impl OpForkTracker {
350 pub(crate) fn is_interop_activated(&self) -> bool {
352 self.interop.load(Ordering::Relaxed)
353 }
354}