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 as_valid_transaction(&self) -> Option<&ValidTransaction<T>> {
79 match self {
80 Self::Valid { transaction, .. } => Some(transaction),
81 _ => None,
82 }
83 }
84
85 pub const fn is_valid(&self) -> bool {
87 matches!(self, Self::Valid { .. })
88 }
89
90 pub const fn is_invalid(&self) -> bool {
92 matches!(self, Self::Invalid(_, _))
93 }
94
95 pub const fn is_error(&self) -> bool {
97 matches!(self, Self::Error(_, _))
98 }
99}
100
101#[derive(Debug)]
111pub enum ValidTransaction<T> {
112 Valid(T),
114 ValidWithSidecar {
119 transaction: T,
121 sidecar: BlobTransactionSidecarVariant,
123 },
124}
125
126impl<T> ValidTransaction<T> {
127 pub fn new(transaction: T, sidecar: Option<BlobTransactionSidecarVariant>) -> Self {
129 if let Some(sidecar) = sidecar {
130 Self::ValidWithSidecar { transaction, sidecar }
131 } else {
132 Self::Valid(transaction)
133 }
134 }
135}
136
137impl<T: PoolTransaction> ValidTransaction<T> {
138 #[inline]
140 pub const fn transaction(&self) -> &T {
141 match self {
142 Self::Valid(transaction) | Self::ValidWithSidecar { transaction, .. } => transaction,
143 }
144 }
145
146 pub fn into_transaction(self) -> T {
148 match self {
149 Self::Valid(transaction) | Self::ValidWithSidecar { transaction, .. } => transaction,
150 }
151 }
152
153 #[inline]
155 pub(crate) fn sender(&self) -> Address {
156 self.transaction().sender()
157 }
158
159 #[inline]
161 pub fn hash(&self) -> &B256 {
162 self.transaction().hash()
163 }
164
165 #[inline]
167 pub fn nonce(&self) -> u64 {
168 self.transaction().nonce()
169 }
170}
171
172pub trait TransactionValidator: Debug + Send + Sync {
174 type Transaction: PoolTransaction;
176
177 fn validate_transaction(
203 &self,
204 origin: TransactionOrigin,
205 transaction: Self::Transaction,
206 ) -> impl Future<Output = TransactionValidationOutcome<Self::Transaction>> + Send;
207
208 fn validate_transactions(
214 &self,
215 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
216 ) -> impl Future<Output = Vec<TransactionValidationOutcome<Self::Transaction>>> + Send {
217 futures_util::future::join_all(
218 transactions.into_iter().map(|(origin, tx)| self.validate_transaction(origin, tx)),
219 )
220 }
221
222 fn validate_transactions_with_origin(
228 &self,
229 origin: TransactionOrigin,
230 transactions: impl IntoIterator<Item = Self::Transaction> + Send,
231 ) -> impl Future<Output = Vec<TransactionValidationOutcome<Self::Transaction>>> + Send {
232 let futures = transactions.into_iter().map(|tx| self.validate_transaction(origin, tx));
233 futures_util::future::join_all(futures)
234 }
235
236 fn on_new_head_block<B>(&self, _new_tip_block: &SealedBlock<B>)
240 where
241 B: Block,
242 {
243 }
244}
245
246impl<A, B> TransactionValidator for Either<A, B>
247where
248 A: TransactionValidator,
249 B: TransactionValidator<Transaction = A::Transaction>,
250{
251 type Transaction = A::Transaction;
252
253 async fn validate_transaction(
254 &self,
255 origin: TransactionOrigin,
256 transaction: Self::Transaction,
257 ) -> TransactionValidationOutcome<Self::Transaction> {
258 match self {
259 Self::Left(v) => v.validate_transaction(origin, transaction).await,
260 Self::Right(v) => v.validate_transaction(origin, transaction).await,
261 }
262 }
263
264 async fn validate_transactions(
265 &self,
266 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
267 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
268 match self {
269 Self::Left(v) => v.validate_transactions(transactions).await,
270 Self::Right(v) => v.validate_transactions(transactions).await,
271 }
272 }
273
274 async fn validate_transactions_with_origin(
275 &self,
276 origin: TransactionOrigin,
277 transactions: impl IntoIterator<Item = Self::Transaction> + Send,
278 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
279 match self {
280 Self::Left(v) => v.validate_transactions_with_origin(origin, transactions).await,
281 Self::Right(v) => v.validate_transactions_with_origin(origin, transactions).await,
282 }
283 }
284
285 fn on_new_head_block<Bl>(&self, new_tip_block: &SealedBlock<Bl>)
286 where
287 Bl: Block,
288 {
289 match self {
290 Self::Left(v) => v.on_new_head_block(new_tip_block),
291 Self::Right(v) => v.on_new_head_block(new_tip_block),
292 }
293 }
294}
295
296pub struct ValidPoolTransaction<T: PoolTransaction> {
303 pub transaction: T,
305 pub transaction_id: TransactionId,
307 pub propagate: bool,
309 pub timestamp: Instant,
311 pub origin: TransactionOrigin,
313 pub authority_ids: Option<Vec<SenderId>>,
315}
316
317impl<T: PoolTransaction> ValidPoolTransaction<T> {
320 pub fn hash(&self) -> &TxHash {
322 self.transaction.hash()
323 }
324
325 pub fn tx_type(&self) -> u8 {
327 self.transaction.ty()
328 }
329
330 pub fn sender(&self) -> Address {
332 self.transaction.sender()
333 }
334
335 pub fn sender_ref(&self) -> &Address {
337 self.transaction.sender_ref()
338 }
339
340 pub fn to(&self) -> Option<Address> {
342 self.transaction.to()
343 }
344
345 pub const fn sender_id(&self) -> SenderId {
347 self.transaction_id.sender
348 }
349
350 pub const fn id(&self) -> &TransactionId {
352 &self.transaction_id
353 }
354
355 #[inline]
357 pub fn encoded_length(&self) -> usize {
358 self.transaction.encoded_length()
359 }
360
361 pub fn nonce(&self) -> u64 {
363 self.transaction.nonce()
364 }
365
366 pub fn cost(&self) -> &U256 {
371 self.transaction.cost()
372 }
373
374 pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
378 self.transaction.max_fee_per_blob_gas()
379 }
380
381 pub fn max_fee_per_gas(&self) -> u128 {
385 self.transaction.max_fee_per_gas()
386 }
387
388 pub fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
393 self.transaction.effective_tip_per_gas(base_fee)
394 }
395
396 pub fn priority_fee_or_price(&self) -> u128 {
399 self.transaction.priority_fee_or_price()
400 }
401
402 pub fn gas_limit(&self) -> u64 {
404 self.transaction.gas_limit()
405 }
406
407 pub const fn is_local(&self) -> bool {
409 self.origin.is_local()
410 }
411
412 #[inline]
414 pub fn is_eip4844(&self) -> bool {
415 self.transaction.is_eip4844()
416 }
417
418 pub(crate) fn size(&self) -> usize {
420 self.transaction.size()
421 }
422
423 pub fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
427 self.transaction.authorization_list()
428 }
429
430 pub fn authorization_count(&self) -> Option<u64> {
436 self.transaction.authorization_count()
437 }
438
439 #[inline]
445 pub(crate) fn tx_type_conflicts_with(&self, other: &Self) -> bool {
446 self.is_eip4844() != other.is_eip4844()
447 }
448
449 pub fn to_consensus(&self) -> Recovered<T::Consensus> {
453 self.transaction.clone_into_consensus()
454 }
455
456 #[inline]
463 pub fn is_underpriced(&self, maybe_replacement: &Self, price_bumps: &PriceBumpConfig) -> bool {
464 let price_bump = price_bumps.price_bump(self.tx_type());
468
469 if maybe_replacement.max_fee_per_gas() < self.max_fee_per_gas() * (100 + price_bump) / 100 {
471 return true
472 }
473
474 let existing_max_priority_fee_per_gas =
475 self.transaction.max_priority_fee_per_gas().unwrap_or_default();
476 let replacement_max_priority_fee_per_gas =
477 maybe_replacement.transaction.max_priority_fee_per_gas().unwrap_or_default();
478
479 if existing_max_priority_fee_per_gas != 0 &&
481 replacement_max_priority_fee_per_gas != 0 &&
482 replacement_max_priority_fee_per_gas <
483 existing_max_priority_fee_per_gas * (100 + price_bump) / 100
484 {
485 return true
486 }
487
488 if let Some(existing_max_blob_fee_per_gas) = self.transaction.max_fee_per_blob_gas() {
490 let replacement_max_blob_fee_per_gas =
492 maybe_replacement.transaction.max_fee_per_blob_gas().unwrap_or_default();
493 if replacement_max_blob_fee_per_gas <
494 existing_max_blob_fee_per_gas * (100 + price_bump) / 100
495 {
496 return true
497 }
498 }
499
500 false
501 }
502}
503
504#[cfg(test)]
505impl<T: PoolTransaction> Clone for ValidPoolTransaction<T> {
506 fn clone(&self) -> Self {
507 Self {
508 transaction: self.transaction.clone(),
509 transaction_id: self.transaction_id,
510 propagate: self.propagate,
511 timestamp: self.timestamp,
512 origin: self.origin,
513 authority_ids: self.authority_ids.clone(),
514 }
515 }
516}
517
518impl<T: PoolTransaction> fmt::Debug for ValidPoolTransaction<T> {
519 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
520 f.debug_struct("ValidPoolTransaction")
521 .field("id", &self.transaction_id)
522 .field("propagate", &self.propagate)
523 .field("origin", &self.origin)
524 .field("hash", self.transaction.hash())
525 .field("tx", &self.transaction)
526 .finish()
527 }
528}
529
530#[derive(thiserror::Error, Debug)]
532pub enum TransactionValidatorError {
533 #[error("validation service unreachable")]
535 ValidationServiceUnreachable,
536}