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::{Block, 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::{DEFAULT_MAX_TX_INPUT_BYTES, TX_SLOT_BYTE_SIZE};
25
26#[derive(Debug)]
28pub enum TransactionValidationOutcome<T: PoolTransaction> {
29 Valid {
31 balance: U256,
33 state_nonce: u64,
35 bytecode_hash: Option<B256>,
37 transaction: ValidTransaction<T>,
44 propagate: bool,
46 authorities: Option<Vec<Address>>,
48 },
49 Invalid(T, InvalidPoolTransactionError),
52 Error(TxHash, Box<dyn core::error::Error + Send + Sync>),
54}
55
56impl<T: PoolTransaction> TransactionValidationOutcome<T> {
57 pub fn tx_hash(&self) -> TxHash {
59 match self {
60 Self::Valid { transaction, .. } => *transaction.hash(),
61 Self::Invalid(transaction, ..) => *transaction.hash(),
62 Self::Error(hash, ..) => *hash,
63 }
64 }
65
66 pub const fn as_invalid(&self) -> Option<&InvalidPoolTransactionError> {
68 match self {
69 Self::Invalid(_, err) => Some(err),
70 _ => None,
71 }
72 }
73
74 pub const fn as_valid_transaction(&self) -> Option<&ValidTransaction<T>> {
76 match self {
77 Self::Valid { transaction, .. } => Some(transaction),
78 _ => None,
79 }
80 }
81
82 pub const fn is_valid(&self) -> bool {
84 matches!(self, Self::Valid { .. })
85 }
86
87 pub const fn is_invalid(&self) -> bool {
89 matches!(self, Self::Invalid(_, _))
90 }
91
92 pub const fn is_error(&self) -> bool {
94 matches!(self, Self::Error(_, _))
95 }
96}
97
98#[derive(Debug)]
108pub enum ValidTransaction<T> {
109 Valid(T),
111 ValidWithSidecar {
116 transaction: T,
118 sidecar: BlobTransactionSidecarVariant,
120 },
121}
122
123impl<T> ValidTransaction<T> {
124 pub fn new(transaction: T, sidecar: Option<BlobTransactionSidecarVariant>) -> Self {
126 if let Some(sidecar) = sidecar {
127 Self::ValidWithSidecar { transaction, sidecar }
128 } else {
129 Self::Valid(transaction)
130 }
131 }
132}
133
134impl<T: PoolTransaction> ValidTransaction<T> {
135 #[inline]
137 pub const fn transaction(&self) -> &T {
138 match self {
139 Self::Valid(transaction) | Self::ValidWithSidecar { transaction, .. } => transaction,
140 }
141 }
142
143 pub fn into_transaction(self) -> T {
145 match self {
146 Self::Valid(transaction) | Self::ValidWithSidecar { transaction, .. } => transaction,
147 }
148 }
149
150 #[inline]
152 pub(crate) fn sender(&self) -> Address {
153 self.transaction().sender()
154 }
155
156 #[inline]
158 pub fn hash(&self) -> &B256 {
159 self.transaction().hash()
160 }
161
162 #[inline]
164 pub fn nonce(&self) -> u64 {
165 self.transaction().nonce()
166 }
167}
168
169pub trait TransactionValidator: Debug + Send + Sync {
171 type Transaction: PoolTransaction;
173
174 type Block: Block;
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: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
216 + Send,
217 ) -> impl Future<Output = Vec<TransactionValidationOutcome<Self::Transaction>>> + Send {
218 futures_util::future::join_all(
219 transactions.into_iter().map(|(origin, tx)| self.validate_transaction(origin, tx)),
220 )
221 }
222
223 fn validate_transactions_with_origin(
229 &self,
230 origin: TransactionOrigin,
231 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
232 ) -> impl Future<Output = Vec<TransactionValidationOutcome<Self::Transaction>>> + Send {
233 self.validate_transactions(transactions.into_iter().map(move |tx| (origin, tx)))
234 }
235
236 fn on_new_head_block(&self, _new_tip_block: &SealedBlock<Self::Block>) {}
240}
241
242impl<A, B> TransactionValidator for Either<A, B>
243where
244 A: TransactionValidator,
245 B: TransactionValidator<Transaction = A::Transaction, Block = A::Block>,
246{
247 type Transaction = A::Transaction;
248 type Block = A::Block;
249
250 async fn validate_transaction(
251 &self,
252 origin: TransactionOrigin,
253 transaction: Self::Transaction,
254 ) -> TransactionValidationOutcome<Self::Transaction> {
255 match self {
256 Self::Left(v) => v.validate_transaction(origin, transaction).await,
257 Self::Right(v) => v.validate_transaction(origin, transaction).await,
258 }
259 }
260
261 async fn validate_transactions(
262 &self,
263 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
264 + Send,
265 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
266 match self {
267 Self::Left(v) => v.validate_transactions(transactions).await,
268 Self::Right(v) => v.validate_transactions(transactions).await,
269 }
270 }
271
272 async fn validate_transactions_with_origin(
273 &self,
274 origin: TransactionOrigin,
275 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
276 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
277 match self {
278 Self::Left(v) => v.validate_transactions_with_origin(origin, transactions).await,
279 Self::Right(v) => v.validate_transactions_with_origin(origin, transactions).await,
280 }
281 }
282
283 fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
284 match self {
285 Self::Left(v) => v.on_new_head_block(new_tip_block),
286 Self::Right(v) => v.on_new_head_block(new_tip_block),
287 }
288 }
289}
290
291pub struct ValidPoolTransaction<T: PoolTransaction> {
298 pub transaction: T,
300 pub transaction_id: TransactionId,
302 pub propagate: bool,
304 pub timestamp: Instant,
306 pub origin: TransactionOrigin,
308 pub authority_ids: Option<Vec<SenderId>>,
310}
311
312impl<T: PoolTransaction> ValidPoolTransaction<T> {
315 pub fn hash(&self) -> &TxHash {
317 self.transaction.hash()
318 }
319
320 pub fn tx_type(&self) -> u8 {
322 self.transaction.ty()
323 }
324
325 pub fn sender(&self) -> Address {
327 self.transaction.sender()
328 }
329
330 pub fn sender_ref(&self) -> &Address {
332 self.transaction.sender_ref()
333 }
334
335 pub fn to(&self) -> Option<Address> {
337 self.transaction.to()
338 }
339
340 pub const fn sender_id(&self) -> SenderId {
342 self.transaction_id.sender
343 }
344
345 pub const fn id(&self) -> &TransactionId {
347 &self.transaction_id
348 }
349
350 #[inline]
352 pub fn encoded_length(&self) -> usize {
353 self.transaction.encoded_length()
354 }
355
356 pub fn nonce(&self) -> u64 {
358 self.transaction.nonce()
359 }
360
361 pub fn cost(&self) -> &U256 {
366 self.transaction.cost()
367 }
368
369 pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
373 self.transaction.max_fee_per_blob_gas()
374 }
375
376 pub fn max_fee_per_gas(&self) -> u128 {
380 self.transaction.max_fee_per_gas()
381 }
382
383 pub fn max_priority_fee_per_gas(&self) -> Option<u128> {
386 self.transaction.max_priority_fee_per_gas()
387 }
388
389 pub fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
394 self.transaction.effective_tip_per_gas(base_fee)
395 }
396
397 pub fn priority_fee_or_price(&self) -> u128 {
400 self.transaction.priority_fee_or_price()
401 }
402
403 pub fn gas_limit(&self) -> u64 {
405 self.transaction.gas_limit()
406 }
407
408 pub const fn is_local(&self) -> bool {
410 self.origin.is_local()
411 }
412
413 #[inline]
415 pub fn is_eip4844(&self) -> bool {
416 self.transaction.is_eip4844()
417 }
418
419 pub(crate) fn size(&self) -> usize {
421 self.transaction.size()
422 }
423
424 pub fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
428 self.transaction.authorization_list()
429 }
430
431 pub fn authorization_count(&self) -> Option<u64> {
437 self.transaction.authorization_count()
438 }
439
440 #[inline]
446 pub(crate) fn tx_type_conflicts_with(&self, other: &Self) -> bool {
447 self.is_eip4844() != other.is_eip4844()
448 }
449
450 pub fn to_consensus(&self) -> Recovered<T::Consensus> {
454 self.transaction.clone_into_consensus()
455 }
456
457 #[inline]
464 pub fn is_underpriced(&self, maybe_replacement: &Self, price_bumps: &PriceBumpConfig) -> bool {
465 let price_bump = price_bumps.price_bump(self.tx_type());
469 let required_bumped_fee =
470 |existing_fee: u128| existing_fee.saturating_mul(100 + price_bump).div_ceil(100);
471
472 if maybe_replacement.max_fee_per_gas() < required_bumped_fee(self.max_fee_per_gas()) {
474 return true
475 }
476
477 let existing_max_priority_fee_per_gas =
478 self.transaction.max_priority_fee_per_gas().unwrap_or_default();
479 let replacement_max_priority_fee_per_gas =
480 maybe_replacement.transaction.max_priority_fee_per_gas().unwrap_or_default();
481
482 if existing_max_priority_fee_per_gas != 0 &&
484 replacement_max_priority_fee_per_gas != 0 &&
485 replacement_max_priority_fee_per_gas <
486 required_bumped_fee(existing_max_priority_fee_per_gas)
487 {
488 return true
489 }
490
491 if let Some(existing_max_blob_fee_per_gas) = self.transaction.max_fee_per_blob_gas() {
493 let replacement_max_blob_fee_per_gas =
495 maybe_replacement.transaction.max_fee_per_blob_gas().unwrap_or_default();
496 if replacement_max_blob_fee_per_gas < required_bumped_fee(existing_max_blob_fee_per_gas)
497 {
498 return true
499 }
500 }
501
502 false
503 }
504}
505
506#[cfg(test)]
507impl<T: PoolTransaction> Clone for ValidPoolTransaction<T> {
508 fn clone(&self) -> Self {
509 Self {
510 transaction: self.transaction.clone(),
511 transaction_id: self.transaction_id,
512 propagate: self.propagate,
513 timestamp: self.timestamp,
514 origin: self.origin,
515 authority_ids: self.authority_ids.clone(),
516 }
517 }
518}
519
520impl<T: PoolTransaction> fmt::Debug for ValidPoolTransaction<T> {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 f.debug_struct("ValidPoolTransaction")
523 .field("id", &self.transaction_id)
524 .field("propagate", &self.propagate)
525 .field("origin", &self.origin)
526 .field("hash", self.transaction.hash())
527 .field("tx", &self.transaction)
528 .finish()
529 }
530}
531
532#[derive(thiserror::Error, Debug)]
534pub enum TransactionValidatorError {
535 #[error("validation service unreachable")]
537 ValidationServiceUnreachable,
538}