mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-08 22:14:57 -05:00
184 lines
8.4 KiB
Rust
184 lines
8.4 KiB
Rust
//! Transaction pool errors
|
|
|
|
use reth_primitives::{Address, InvalidTransactionError, TxHash};
|
|
|
|
/// Transaction pool result type.
|
|
pub type PoolResult<T> = Result<T, PoolError>;
|
|
|
|
/// All errors the Transaction pool can throw.
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum PoolError {
|
|
/// Same transaction already imported
|
|
#[error("[{0:?}] Already imported")]
|
|
AlreadyImported(TxHash),
|
|
/// Thrown if a replacement transaction's gas price is below the already imported transaction
|
|
#[error("[{0:?}]: insufficient gas price to replace existing transaction.")]
|
|
ReplacementUnderpriced(TxHash),
|
|
/// The fee cap of the transaction is below the minimum fee cap determined by the protocol
|
|
#[error("[{0:?}] Transaction feeCap {1} below chain minimum.")]
|
|
FeeCapBelowMinimumProtocolFeeCap(TxHash, u128),
|
|
/// Thrown when the number of unique transactions of a sender exceeded the slot capacity.
|
|
#[error("{0:?} identified as spammer. Transaction {1:?} rejected.")]
|
|
SpammerExceededCapacity(Address, TxHash),
|
|
/// Thrown when a new transaction is added to the pool, but then immediately discarded to
|
|
/// respect the size limits of the pool.
|
|
#[error("[{0:?}] Transaction discarded outright due to pool size constraints.")]
|
|
DiscardedOnInsert(TxHash),
|
|
/// Thrown when the transaction is considered invalid.
|
|
#[error("[{0:?}] {1:?}")]
|
|
InvalidTransaction(TxHash, InvalidPoolTransactionError),
|
|
/// Any other error that occurred while inserting/validating a transaction. e.g. IO database
|
|
/// error
|
|
#[error("[{0:?}] {1:?}")]
|
|
Other(TxHash, Box<dyn std::error::Error + Send + Sync>),
|
|
}
|
|
|
|
// === impl PoolError ===
|
|
|
|
impl PoolError {
|
|
/// Returns the hash of the transaction that resulted in this error.
|
|
pub fn hash(&self) -> &TxHash {
|
|
match self {
|
|
PoolError::AlreadyImported(hash) => hash,
|
|
PoolError::ReplacementUnderpriced(hash) => hash,
|
|
PoolError::FeeCapBelowMinimumProtocolFeeCap(hash, _) => hash,
|
|
PoolError::SpammerExceededCapacity(_, hash) => hash,
|
|
PoolError::DiscardedOnInsert(hash) => hash,
|
|
PoolError::InvalidTransaction(hash, _) => hash,
|
|
PoolError::Other(hash, _) => hash,
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if the error was caused by a transaction that is considered bad in the
|
|
/// context of the transaction pool and warrants peer penalization.
|
|
///
|
|
/// Not all error variants are caused by the incorrect composition of the transaction (See also
|
|
/// [InvalidPoolTransactionError]) and can be caused by the current state of the transaction
|
|
/// pool. For example the transaction pool is already full or the error was caused my an
|
|
/// internal error, such as database errors.
|
|
///
|
|
/// This function returns true only if the transaction will never make it into the pool because
|
|
/// its composition is invalid and the original sender should have detected this as well. This
|
|
/// is used to determine whether the original sender should be penalized for sending an
|
|
/// erroneous transaction.
|
|
#[inline]
|
|
pub fn is_bad_transaction(&self) -> bool {
|
|
match self {
|
|
PoolError::AlreadyImported(_) => {
|
|
// already imported but not bad
|
|
false
|
|
}
|
|
PoolError::ReplacementUnderpriced(_) => {
|
|
// already imported but not bad
|
|
false
|
|
}
|
|
PoolError::FeeCapBelowMinimumProtocolFeeCap(_, _) => {
|
|
// fee cap of the tx below the technical minimum determined by the protocol, see
|
|
// [MINIMUM_PROTOCOL_FEE_CAP](reth_primitives::constants::MIN_PROTOCOL_BASE_FEE)
|
|
// although this transaction will always be invalid, we do not want to penalize the
|
|
// sender because this check simply could not be implemented by the client
|
|
false
|
|
}
|
|
PoolError::SpammerExceededCapacity(_, _) => {
|
|
// the sender exceeded the slot capacity, we should not penalize the peer for
|
|
// sending the tx because we don't know if all the transactions are sent from the
|
|
// same peer, there's also a chance that old transactions haven't been cleared yet
|
|
// (pool lags behind) and old transaction still occupy a slot in the pool
|
|
false
|
|
}
|
|
PoolError::DiscardedOnInsert(_) => {
|
|
// valid tx but dropped due to size constraints
|
|
false
|
|
}
|
|
PoolError::InvalidTransaction(_, err) => {
|
|
// transaction rejected because it violates constraints
|
|
err.is_bad_transaction()
|
|
}
|
|
PoolError::Other(_, _) => {
|
|
// internal error unrelated to the transaction
|
|
false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents errors that can happen when validating transactions for the pool
|
|
///
|
|
/// See [TransactionValidator](crate::TransactionValidator).
|
|
#[derive(Debug, Clone, thiserror::Error)]
|
|
pub enum InvalidPoolTransactionError {
|
|
/// Hard consensus errors
|
|
#[error(transparent)]
|
|
Consensus(#[from] InvalidTransactionError),
|
|
/// Thrown when a new transaction is added to the pool, but then immediately discarded to
|
|
/// respect the size limits of the pool.
|
|
#[error("Transaction's gas limit {0} exceeds block's gas limit {1}.")]
|
|
ExceedsGasLimit(u64, u64),
|
|
/// Thrown when a new transaction is added to the pool, but then immediately discarded to
|
|
/// respect the max_init_code_size.
|
|
#[error("Transaction's size {0} exceeds max_init_code_size {1}.")]
|
|
ExceedsMaxInitCodeSize(usize, usize),
|
|
/// Thrown if the input data of a transaction is greater
|
|
/// than some meaningful limit a user might use. This is not a consensus error
|
|
/// making the transaction invalid, rather a DOS protection.
|
|
#[error("Input data too large")]
|
|
OversizedData(usize, usize),
|
|
/// Thrown if the transaction's fee is below the minimum fee
|
|
#[error("transaction underpriced")]
|
|
Underpriced,
|
|
}
|
|
|
|
// === impl InvalidPoolTransactionError ===
|
|
|
|
impl InvalidPoolTransactionError {
|
|
/// Returns `true` if the error was caused by a transaction that is considered bad in the
|
|
/// context of the transaction pool and warrants peer penalization.
|
|
///
|
|
/// See [PoolError::is_bad_transaction].
|
|
#[inline]
|
|
fn is_bad_transaction(&self) -> bool {
|
|
match self {
|
|
InvalidPoolTransactionError::Consensus(err) => {
|
|
// transaction considered invalid by the consensus rules
|
|
// We do not consider the following errors to be erroneous transactions, since they
|
|
// depend on dynamic environmental conditions and should not be assumed to have been
|
|
// intentionally caused by the sender
|
|
match err {
|
|
InvalidTransactionError::InsufficientFunds { .. } |
|
|
InvalidTransactionError::NonceNotConsistent => {
|
|
// transaction could just have arrived late/early
|
|
false
|
|
}
|
|
InvalidTransactionError::GasTooLow |
|
|
InvalidTransactionError::GasTooHigh |
|
|
InvalidTransactionError::TipAboveFeeCap => {
|
|
// these are technically not invalid
|
|
false
|
|
}
|
|
InvalidTransactionError::FeeCapTooLow => {
|
|
// dynamic, but not used during validation
|
|
false
|
|
}
|
|
InvalidTransactionError::Eip2930Disabled |
|
|
InvalidTransactionError::Eip1559Disabled => {
|
|
// settings
|
|
false
|
|
}
|
|
InvalidTransactionError::OldLegacyChainId => true,
|
|
InvalidTransactionError::ChainIdMismatch => true,
|
|
InvalidTransactionError::GasUintOverflow => true,
|
|
InvalidTransactionError::TxTypeNotSupported => true,
|
|
InvalidTransactionError::SignerAccountHasBytecode => true,
|
|
}
|
|
}
|
|
InvalidPoolTransactionError::ExceedsGasLimit(_, _) => true,
|
|
InvalidPoolTransactionError::ExceedsMaxInitCodeSize(_, _) => true,
|
|
InvalidPoolTransactionError::OversizedData(_, _) => true,
|
|
InvalidPoolTransactionError::Underpriced => {
|
|
// local setting
|
|
false
|
|
}
|
|
}
|
|
}
|
|
}
|