reth_transaction_pool/validate/
mod.rs1use crate::{
4 blobstore::PooledBlobSidecar,
5 error::InvalidPoolTransactionError,
6 identifier::{SenderId, TransactionId},
7 traits::{PoolTransaction, TransactionOrigin},
8 PriceBumpConfig,
9};
10use alloy_eips::eip7702::SignedAuthorization;
11use alloy_primitives::{Address, TxHash, B256, U256};
12use futures_util::future::Either;
13use reth_primitives_traits::{Block, Recovered, SealedBlock};
14use std::{fmt, fmt::Debug, future::Future, time::Instant};
15
16mod constants;
17mod eth;
18mod task;
19
20pub use eth::*;
21
22pub use task::{TransactionValidationTaskExecutor, ValidationTask};
23
24pub use constants::{DEFAULT_MAX_TX_INPUT_BYTES, TX_SLOT_BYTE_SIZE};
26
27#[derive(Debug)]
29pub enum TransactionValidationOutcome<T: PoolTransaction> {
30 Valid {
32 balance: U256,
34 state_nonce: u64,
36 bytecode_hash: Option<B256>,
38 transaction: ValidTransaction<T>,
45 propagate: bool,
47 authorities: Option<Vec<Address>>,
49 },
50 Invalid(T, InvalidPoolTransactionError),
53 Error(TxHash, Box<dyn core::error::Error + Send + Sync>),
55}
56
57impl<T: PoolTransaction> TransactionValidationOutcome<T> {
58 pub fn tx_hash(&self) -> TxHash {
60 match self {
61 Self::Valid { transaction, .. } => *transaction.hash(),
62 Self::Invalid(transaction, ..) => *transaction.hash(),
63 Self::Error(hash, ..) => *hash,
64 }
65 }
66
67 pub const fn as_invalid(&self) -> Option<&InvalidPoolTransactionError> {
69 match self {
70 Self::Invalid(_, err) => Some(err),
71 _ => None,
72 }
73 }
74
75 pub const fn as_valid_transaction(&self) -> Option<&ValidTransaction<T>> {
77 match self {
78 Self::Valid { transaction, .. } => Some(transaction),
79 _ => None,
80 }
81 }
82
83 pub const fn is_valid(&self) -> bool {
85 matches!(self, Self::Valid { .. })
86 }
87
88 pub const fn is_invalid(&self) -> bool {
90 matches!(self, Self::Invalid(_, _))
91 }
92
93 pub const fn is_error(&self) -> bool {
95 matches!(self, Self::Error(_, _))
96 }
97}
98
99#[derive(Debug)]
109pub enum ValidTransaction<T> {
110 Valid(T),
112 ValidWithSidecar {
117 transaction: T,
119 sidecar: PooledBlobSidecar,
121 },
122}
123
124impl<T> ValidTransaction<T> {
125 pub fn new(transaction: T, sidecar: Option<PooledBlobSidecar>) -> Self {
127 if let Some(sidecar) = sidecar {
128 Self::ValidWithSidecar { transaction, sidecar }
129 } else {
130 Self::Valid(transaction)
131 }
132 }
133}
134
135impl<T: PoolTransaction> ValidTransaction<T> {
136 #[inline]
138 pub const fn transaction(&self) -> &T {
139 match self {
140 Self::Valid(transaction) | Self::ValidWithSidecar { transaction, .. } => transaction,
141 }
142 }
143
144 pub fn into_transaction(self) -> T {
146 match self {
147 Self::Valid(transaction) | Self::ValidWithSidecar { transaction, .. } => transaction,
148 }
149 }
150
151 #[inline]
153 pub(crate) fn sender(&self) -> Address {
154 self.transaction().sender()
155 }
156
157 #[inline]
159 pub fn hash(&self) -> &B256 {
160 self.transaction().hash()
161 }
162
163 #[inline]
165 pub fn nonce(&self) -> u64 {
166 self.transaction().nonce()
167 }
168}
169
170pub trait TransactionValidator: Debug + Send + Sync {
172 type Transaction: PoolTransaction;
174
175 type Block: Block;
177
178 fn validate_transaction(
204 &self,
205 origin: TransactionOrigin,
206 transaction: Self::Transaction,
207 ) -> impl Future<Output = TransactionValidationOutcome<Self::Transaction>> + Send;
208
209 fn validate_transactions(
215 &self,
216 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
217 + Send,
218 ) -> impl Future<Output = Vec<TransactionValidationOutcome<Self::Transaction>>> + Send {
219 futures_util::future::join_all(
220 transactions.into_iter().map(|(origin, tx)| self.validate_transaction(origin, tx)),
221 )
222 }
223
224 fn validate_transactions_with_origin(
230 &self,
231 origin: TransactionOrigin,
232 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
233 ) -> impl Future<Output = Vec<TransactionValidationOutcome<Self::Transaction>>> + Send {
234 self.validate_transactions(transactions.into_iter().map(move |tx| (origin, tx)))
235 }
236
237 fn on_new_head_block(&self, _new_tip_block: &SealedBlock<Self::Block>) {}
241}
242
243impl<A, B> TransactionValidator for Either<A, B>
244where
245 A: TransactionValidator,
246 B: TransactionValidator<Transaction = A::Transaction, Block = A::Block>,
247{
248 type Transaction = A::Transaction;
249 type Block = A::Block;
250
251 async fn validate_transaction(
252 &self,
253 origin: TransactionOrigin,
254 transaction: Self::Transaction,
255 ) -> TransactionValidationOutcome<Self::Transaction> {
256 match self {
257 Self::Left(v) => v.validate_transaction(origin, transaction).await,
258 Self::Right(v) => v.validate_transaction(origin, transaction).await,
259 }
260 }
261
262 async fn validate_transactions(
263 &self,
264 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
265 + Send,
266 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
267 match self {
268 Self::Left(v) => v.validate_transactions(transactions).await,
269 Self::Right(v) => v.validate_transactions(transactions).await,
270 }
271 }
272
273 async fn validate_transactions_with_origin(
274 &self,
275 origin: TransactionOrigin,
276 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
277 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
278 match self {
279 Self::Left(v) => v.validate_transactions_with_origin(origin, transactions).await,
280 Self::Right(v) => v.validate_transactions_with_origin(origin, transactions).await,
281 }
282 }
283
284 fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
285 match self {
286 Self::Left(v) => v.on_new_head_block(new_tip_block),
287 Self::Right(v) => v.on_new_head_block(new_tip_block),
288 }
289 }
290}
291
292pub struct ValidPoolTransaction<T: PoolTransaction> {
299 pub transaction: T,
301 pub transaction_id: TransactionId,
303 pub propagate: bool,
305 pub timestamp: Instant,
307 pub origin: TransactionOrigin,
309 pub authority_ids: Option<Vec<SenderId>>,
311}
312
313impl<T: PoolTransaction> ValidPoolTransaction<T> {
316 pub fn hash(&self) -> &TxHash {
318 self.transaction.hash()
319 }
320
321 pub fn tx_type(&self) -> u8 {
323 self.transaction.ty()
324 }
325
326 pub fn sender(&self) -> Address {
328 self.transaction.sender()
329 }
330
331 pub fn sender_ref(&self) -> &Address {
333 self.transaction.sender_ref()
334 }
335
336 pub fn to(&self) -> Option<Address> {
338 self.transaction.to()
339 }
340
341 pub const fn sender_id(&self) -> SenderId {
343 self.transaction_id.sender
344 }
345
346 pub const fn id(&self) -> &TransactionId {
348 &self.transaction_id
349 }
350
351 #[inline]
353 pub fn encoded_length(&self) -> usize {
354 self.transaction.encoded_length()
355 }
356
357 pub fn nonce(&self) -> u64 {
359 self.transaction.nonce()
360 }
361
362 pub fn cost(&self) -> &U256 {
367 self.transaction.cost()
368 }
369
370 pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
374 self.transaction.max_fee_per_blob_gas()
375 }
376
377 pub fn max_fee_per_gas(&self) -> u128 {
381 self.transaction.max_fee_per_gas()
382 }
383
384 pub fn max_priority_fee_per_gas(&self) -> Option<u128> {
387 self.transaction.max_priority_fee_per_gas()
388 }
389
390 pub fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
395 self.transaction.effective_tip_per_gas(base_fee)
396 }
397
398 pub fn priority_fee_or_price(&self) -> u128 {
401 self.transaction.priority_fee_or_price()
402 }
403
404 pub fn gas_limit(&self) -> u64 {
406 self.transaction.gas_limit()
407 }
408
409 pub const fn is_local(&self) -> bool {
411 self.origin.is_local()
412 }
413
414 #[inline]
416 pub fn is_eip4844(&self) -> bool {
417 self.transaction.is_eip4844()
418 }
419
420 pub(crate) fn size(&self) -> usize {
422 self.transaction.size()
423 }
424
425 pub fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
429 self.transaction.authorization_list()
430 }
431
432 pub fn authorization_count(&self) -> Option<u64> {
438 self.transaction.authorization_count()
439 }
440
441 #[inline]
447 pub(crate) fn tx_type_conflicts_with(&self, other: &Self) -> bool {
448 self.is_eip4844() != other.is_eip4844()
449 }
450
451 pub fn to_consensus(&self) -> Recovered<T::Consensus> {
455 self.transaction.clone_into_consensus()
456 }
457
458 #[inline]
465 pub fn is_underpriced(&self, maybe_replacement: &Self, price_bumps: &PriceBumpConfig) -> bool {
466 let price_bump = price_bumps.price_bump(self.tx_type());
470 let required_bumped_fee =
471 |existing_fee: u128| existing_fee.saturating_mul(100 + price_bump).div_ceil(100);
472
473 if maybe_replacement.max_fee_per_gas() < required_bumped_fee(self.max_fee_per_gas()) {
475 return true
476 }
477
478 let existing_max_priority_fee_per_gas =
479 self.transaction.max_priority_fee_per_gas().unwrap_or_default();
480 let replacement_max_priority_fee_per_gas =
481 maybe_replacement.transaction.max_priority_fee_per_gas().unwrap_or_default();
482
483 if existing_max_priority_fee_per_gas != 0 &&
485 replacement_max_priority_fee_per_gas != 0 &&
486 replacement_max_priority_fee_per_gas <
487 required_bumped_fee(existing_max_priority_fee_per_gas)
488 {
489 return true
490 }
491
492 if let Some(existing_max_blob_fee_per_gas) = self.transaction.max_fee_per_blob_gas() {
494 let replacement_max_blob_fee_per_gas =
496 maybe_replacement.transaction.max_fee_per_blob_gas().unwrap_or_default();
497 if replacement_max_blob_fee_per_gas < required_bumped_fee(existing_max_blob_fee_per_gas)
498 {
499 return true
500 }
501 }
502
503 false
504 }
505}
506
507#[cfg(test)]
508impl<T: PoolTransaction> Clone for ValidPoolTransaction<T> {
509 fn clone(&self) -> Self {
510 Self {
511 transaction: self.transaction.clone(),
512 transaction_id: self.transaction_id,
513 propagate: self.propagate,
514 timestamp: self.timestamp,
515 origin: self.origin,
516 authority_ids: self.authority_ids.clone(),
517 }
518 }
519}
520
521impl<T: PoolTransaction> fmt::Debug for ValidPoolTransaction<T> {
522 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523 f.debug_struct("ValidPoolTransaction")
524 .field("id", &self.transaction_id)
525 .field("propagate", &self.propagate)
526 .field("origin", &self.origin)
527 .field("hash", self.transaction.hash())
528 .field("tx", &self.transaction)
529 .finish()
530 }
531}
532
533#[derive(thiserror::Error, Debug)]
535pub enum TransactionValidatorError {
536 #[error("validation service unreachable")]
538 ValidationServiceUnreachable,
539}