reth_transaction_pool/validate/
mod.rs1use crate::{
4 error::InvalidPoolTransactionError,
5 identifier::{SenderId, TransactionId},
6 traits::{PoolTransaction, TransactionOrigin},
7 PriceBumpConfig,
8};
9use alloy_eips::{eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization};
10use alloy_primitives::{Address, TxHash, B256, U256};
11use futures_util::future::Either;
12use reth_primitives_traits::{Recovered, SealedBlock};
13use std::{fmt, fmt::Debug, future::Future, time::Instant};
14
15mod constants;
16mod eth;
17mod task;
18
19pub use eth::*;
20
21pub use task::{TransactionValidationTaskExecutor, ValidationTask};
22
23pub use constants::{
25 DEFAULT_MAX_TX_INPUT_BYTES, MAX_CODE_BYTE_SIZE, MAX_INIT_CODE_BYTE_SIZE, TX_SLOT_BYTE_SIZE,
26};
27use reth_primitives_traits::Block;
28
29#[derive(Debug)]
31pub enum TransactionValidationOutcome<T: PoolTransaction> {
32 Valid {
34 balance: U256,
36 state_nonce: u64,
38 bytecode_hash: Option<B256>,
40 transaction: ValidTransaction<T>,
47 propagate: bool,
49 authorities: Option<Vec<Address>>,
51 },
52 Invalid(T, InvalidPoolTransactionError),
55 Error(TxHash, Box<dyn core::error::Error + Send + Sync>),
57}
58
59impl<T: PoolTransaction> TransactionValidationOutcome<T> {
60 pub fn tx_hash(&self) -> TxHash {
62 match self {
63 Self::Valid { transaction, .. } => *transaction.hash(),
64 Self::Invalid(transaction, ..) => *transaction.hash(),
65 Self::Error(hash, ..) => *hash,
66 }
67 }
68
69 pub const fn as_invalid(&self) -> Option<&InvalidPoolTransactionError> {
71 match self {
72 Self::Invalid(_, err) => Some(err),
73 _ => None,
74 }
75 }
76
77 pub const fn is_valid(&self) -> bool {
79 matches!(self, Self::Valid { .. })
80 }
81
82 pub const fn is_invalid(&self) -> bool {
84 matches!(self, Self::Invalid(_, _))
85 }
86
87 pub const fn is_error(&self) -> bool {
89 matches!(self, Self::Error(_, _))
90 }
91}
92
93#[derive(Debug)]
103pub enum ValidTransaction<T> {
104 Valid(T),
106 ValidWithSidecar {
111 transaction: T,
113 sidecar: BlobTransactionSidecarVariant,
115 },
116}
117
118impl<T> ValidTransaction<T> {
119 pub fn new(transaction: T, sidecar: Option<BlobTransactionSidecarVariant>) -> Self {
121 if let Some(sidecar) = sidecar {
122 Self::ValidWithSidecar { transaction, sidecar }
123 } else {
124 Self::Valid(transaction)
125 }
126 }
127}
128
129impl<T: PoolTransaction> ValidTransaction<T> {
130 #[inline]
132 pub const fn transaction(&self) -> &T {
133 match self {
134 Self::Valid(transaction) | Self::ValidWithSidecar { transaction, .. } => transaction,
135 }
136 }
137
138 pub fn into_transaction(self) -> T {
140 match self {
141 Self::Valid(transaction) | Self::ValidWithSidecar { transaction, .. } => transaction,
142 }
143 }
144
145 #[inline]
147 pub(crate) fn sender(&self) -> Address {
148 self.transaction().sender()
149 }
150
151 #[inline]
153 pub fn hash(&self) -> &B256 {
154 self.transaction().hash()
155 }
156
157 #[inline]
159 pub fn nonce(&self) -> u64 {
160 self.transaction().nonce()
161 }
162}
163
164pub trait TransactionValidator: Debug + Send + Sync {
166 type Transaction: PoolTransaction;
168
169 fn validate_transaction(
195 &self,
196 origin: TransactionOrigin,
197 transaction: Self::Transaction,
198 ) -> impl Future<Output = TransactionValidationOutcome<Self::Transaction>> + Send;
199
200 fn validate_transactions(
206 &self,
207 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
208 ) -> impl Future<Output = Vec<TransactionValidationOutcome<Self::Transaction>>> + Send {
209 futures_util::future::join_all(
210 transactions.into_iter().map(|(origin, tx)| self.validate_transaction(origin, tx)),
211 )
212 }
213
214 fn validate_transactions_with_origin(
220 &self,
221 origin: TransactionOrigin,
222 transactions: impl IntoIterator<Item = Self::Transaction> + Send,
223 ) -> impl Future<Output = Vec<TransactionValidationOutcome<Self::Transaction>>> + Send {
224 let futures = transactions.into_iter().map(|tx| self.validate_transaction(origin, tx));
225 futures_util::future::join_all(futures)
226 }
227
228 fn on_new_head_block<B>(&self, _new_tip_block: &SealedBlock<B>)
232 where
233 B: Block,
234 {
235 }
236}
237
238impl<A, B> TransactionValidator for Either<A, B>
239where
240 A: TransactionValidator,
241 B: TransactionValidator<Transaction = A::Transaction>,
242{
243 type Transaction = A::Transaction;
244
245 async fn validate_transaction(
246 &self,
247 origin: TransactionOrigin,
248 transaction: Self::Transaction,
249 ) -> TransactionValidationOutcome<Self::Transaction> {
250 match self {
251 Self::Left(v) => v.validate_transaction(origin, transaction).await,
252 Self::Right(v) => v.validate_transaction(origin, transaction).await,
253 }
254 }
255
256 async fn validate_transactions(
257 &self,
258 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
259 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
260 match self {
261 Self::Left(v) => v.validate_transactions(transactions).await,
262 Self::Right(v) => v.validate_transactions(transactions).await,
263 }
264 }
265
266 async fn validate_transactions_with_origin(
267 &self,
268 origin: TransactionOrigin,
269 transactions: impl IntoIterator<Item = Self::Transaction> + Send,
270 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
271 match self {
272 Self::Left(v) => v.validate_transactions_with_origin(origin, transactions).await,
273 Self::Right(v) => v.validate_transactions_with_origin(origin, transactions).await,
274 }
275 }
276
277 fn on_new_head_block<Bl>(&self, new_tip_block: &SealedBlock<Bl>)
278 where
279 Bl: Block,
280 {
281 match self {
282 Self::Left(v) => v.on_new_head_block(new_tip_block),
283 Self::Right(v) => v.on_new_head_block(new_tip_block),
284 }
285 }
286}
287
288pub struct ValidPoolTransaction<T: PoolTransaction> {
295 pub transaction: T,
297 pub transaction_id: TransactionId,
299 pub propagate: bool,
301 pub timestamp: Instant,
303 pub origin: TransactionOrigin,
305 pub authority_ids: Option<Vec<SenderId>>,
307}
308
309impl<T: PoolTransaction> ValidPoolTransaction<T> {
312 pub fn hash(&self) -> &TxHash {
314 self.transaction.hash()
315 }
316
317 pub fn tx_type(&self) -> u8 {
319 self.transaction.ty()
320 }
321
322 pub fn sender(&self) -> Address {
324 self.transaction.sender()
325 }
326
327 pub fn sender_ref(&self) -> &Address {
329 self.transaction.sender_ref()
330 }
331
332 pub fn to(&self) -> Option<Address> {
334 self.transaction.to()
335 }
336
337 pub(crate) const fn sender_id(&self) -> SenderId {
339 self.transaction_id.sender
340 }
341
342 pub(crate) const fn id(&self) -> &TransactionId {
344 &self.transaction_id
345 }
346
347 #[inline]
349 pub fn encoded_length(&self) -> usize {
350 self.transaction.encoded_length()
351 }
352
353 pub fn nonce(&self) -> u64 {
355 self.transaction.nonce()
356 }
357
358 pub fn cost(&self) -> &U256 {
363 self.transaction.cost()
364 }
365
366 pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
370 self.transaction.max_fee_per_blob_gas()
371 }
372
373 pub fn max_fee_per_gas(&self) -> u128 {
377 self.transaction.max_fee_per_gas()
378 }
379
380 pub fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
385 self.transaction.effective_tip_per_gas(base_fee)
386 }
387
388 pub fn priority_fee_or_price(&self) -> u128 {
391 self.transaction.priority_fee_or_price()
392 }
393
394 pub fn gas_limit(&self) -> u64 {
396 self.transaction.gas_limit()
397 }
398
399 pub const fn is_local(&self) -> bool {
401 self.origin.is_local()
402 }
403
404 #[inline]
406 pub fn is_eip4844(&self) -> bool {
407 self.transaction.is_eip4844()
408 }
409
410 pub(crate) fn size(&self) -> usize {
412 self.transaction.size()
413 }
414
415 pub fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
419 self.transaction.authorization_list()
420 }
421
422 pub fn authorization_count(&self) -> Option<u64> {
428 self.transaction.authorization_count()
429 }
430
431 #[inline]
437 pub(crate) fn tx_type_conflicts_with(&self, other: &Self) -> bool {
438 self.is_eip4844() != other.is_eip4844()
439 }
440
441 pub fn to_consensus(&self) -> Recovered<T::Consensus> {
445 self.transaction.clone_into_consensus()
446 }
447
448 #[inline]
455 pub(crate) fn is_underpriced(
456 &self,
457 maybe_replacement: &Self,
458 price_bumps: &PriceBumpConfig,
459 ) -> bool {
460 let price_bump = price_bumps.price_bump(self.tx_type());
464
465 if maybe_replacement.max_fee_per_gas() < self.max_fee_per_gas() * (100 + price_bump) / 100 {
467 return true
468 }
469
470 let existing_max_priority_fee_per_gas =
471 self.transaction.max_priority_fee_per_gas().unwrap_or_default();
472 let replacement_max_priority_fee_per_gas =
473 maybe_replacement.transaction.max_priority_fee_per_gas().unwrap_or_default();
474
475 if existing_max_priority_fee_per_gas != 0 &&
477 replacement_max_priority_fee_per_gas != 0 &&
478 replacement_max_priority_fee_per_gas <
479 existing_max_priority_fee_per_gas * (100 + price_bump) / 100
480 {
481 return true
482 }
483
484 if let Some(existing_max_blob_fee_per_gas) = self.transaction.max_fee_per_blob_gas() {
486 let replacement_max_blob_fee_per_gas =
488 maybe_replacement.transaction.max_fee_per_blob_gas().unwrap_or_default();
489 if replacement_max_blob_fee_per_gas <
490 existing_max_blob_fee_per_gas * (100 + price_bump) / 100
491 {
492 return true
493 }
494 }
495
496 false
497 }
498}
499
500#[cfg(test)]
501impl<T: PoolTransaction> Clone for ValidPoolTransaction<T> {
502 fn clone(&self) -> Self {
503 Self {
504 transaction: self.transaction.clone(),
505 transaction_id: self.transaction_id,
506 propagate: self.propagate,
507 timestamp: self.timestamp,
508 origin: self.origin,
509 authority_ids: self.authority_ids.clone(),
510 }
511 }
512}
513
514impl<T: PoolTransaction> fmt::Debug for ValidPoolTransaction<T> {
515 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
516 f.debug_struct("ValidPoolTransaction")
517 .field("id", &self.transaction_id)
518 .field("pragate", &self.propagate)
519 .field("origin", &self.origin)
520 .field("hash", self.transaction.hash())
521 .field("tx", &self.transaction)
522 .finish()
523 }
524}
525
526#[derive(thiserror::Error, Debug)]
528pub enum TransactionValidatorError {
529 #[error("validation service unreachable")]
531 ValidationServiceUnreachable,
532}