From 505be4555997790713ca682dcbc08d3b3c509012 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 29 Aug 2023 11:22:28 -0700 Subject: [PATCH] feat: integrate price bump (#4398) --- bin/reth/src/args/txpool_args.rs | 14 ++++-- crates/primitives/src/transaction/mod.rs | 15 +++---- crates/transaction-pool/src/config.rs | 21 +++++++-- crates/transaction-pool/src/pool/txpool.rs | 52 +++++++++++++--------- 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/bin/reth/src/args/txpool_args.rs b/bin/reth/src/args/txpool_args.rs index 51d102a0a..0f01f5c42 100644 --- a/bin/reth/src/args/txpool_args.rs +++ b/bin/reth/src/args/txpool_args.rs @@ -2,8 +2,9 @@ use clap::Args; use reth_transaction_pool::{ - PoolConfig, SubPoolLimit, DEFAULT_PRICE_BUMP, TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER, - TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT, TXPOOL_SUBPOOL_MAX_TXS_DEFAULT, + PoolConfig, PriceBumpConfig, SubPoolLimit, DEFAULT_PRICE_BUMP, REPLACE_BLOB_PRICE_BUMP, + TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER, TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT, + TXPOOL_SUBPOOL_MAX_TXS_DEFAULT, }; /// Parameters for debugging purposes @@ -37,6 +38,10 @@ pub struct TxPoolArgs { /// Price bump (in %) for the transaction pool underpriced check. #[arg(long = "txpool.pricebump", help_heading = "TxPool", default_value_t = DEFAULT_PRICE_BUMP)] pub price_bump: u128, + + /// Price bump percentage to replace an already existing blob transaction + #[arg(long = "blobpool.pricebump", help_heading = "TxPool", default_value_t = REPLACE_BLOB_PRICE_BUMP)] + pub blob_transaction_price_bump: u128, } impl TxPoolArgs { @@ -56,7 +61,10 @@ impl TxPoolArgs { max_size: self.queued_max_size * 1024 * 1024, }, max_account_slots: self.max_account_slots, - price_bump: self.price_bump, + price_bumps: PriceBumpConfig { + default_price_bump: self.price_bump, + replace_blob_tx_price_bump: self.blob_transaction_price_bump, + }, } } } diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index ae420d4f7..a47471e96 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -5,9 +5,16 @@ use crate::{ pub use access_list::{AccessList, AccessListItem, AccessListWithGasUsed}; use bytes::{Buf, BytesMut}; use derive_more::{AsRef, Deref}; +pub use eip1559::TxEip1559; +pub use eip2930::TxEip2930; +pub use eip4844::{ + BlobTransaction, BlobTransactionSidecar, BlobTransactionValidationError, TxEip4844, +}; pub use error::InvalidTransactionError; +pub use legacy::TxLegacy; pub use meta::TransactionMeta; use once_cell::sync::Lazy; +pub use pooled::{PooledTransactionsElement, PooledTransactionsElementEcRecovered}; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use reth_codecs::{add_arbitrary_tests, derive_arbitrary, Compact}; use reth_rlp::{Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE}; @@ -18,14 +25,6 @@ pub use tx_type::{ TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID, }; -pub use eip1559::TxEip1559; -pub use eip2930::TxEip2930; -pub use eip4844::{ - BlobTransaction, BlobTransactionSidecar, BlobTransactionValidationError, TxEip4844, -}; -pub use legacy::TxLegacy; -pub use pooled::{PooledTransactionsElement, PooledTransactionsElementEcRecovered}; - mod access_list; mod eip1559; mod eip2930; diff --git a/crates/transaction-pool/src/config.rs b/crates/transaction-pool/src/config.rs index aa6896305..2e561e35f 100644 --- a/crates/transaction-pool/src/config.rs +++ b/crates/transaction-pool/src/config.rs @@ -1,3 +1,5 @@ +use reth_primitives::EIP4844_TX_TYPE_ID; + /// Guarantees max transactions for one sender, compatible with geth/erigon pub const TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER: usize = 16; @@ -11,6 +13,8 @@ pub const TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT: usize = 20; pub const DEFAULT_PRICE_BUMP: u128 = 10; /// Replace blob price bump (in %) for the transaction pool underpriced check. +/// +/// This enforces that a blob transaction requires a 100% price bump to be replaced pub const REPLACE_BLOB_PRICE_BUMP: u128 = 100; /// Configuration options for the Transaction pool. @@ -25,7 +29,7 @@ pub struct PoolConfig { /// Max number of executable transaction slots guaranteed per account pub max_account_slots: usize, /// Price bump (in %) for the transaction pool underpriced check. - pub price_bump: u128, + pub price_bumps: PriceBumpConfig, } impl Default for PoolConfig { @@ -35,7 +39,7 @@ impl Default for PoolConfig { basefee_limit: Default::default(), queued_limit: Default::default(), max_account_slots: TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER, - price_bump: PriceBumpConfig::default().default_price_bump, + price_bumps: Default::default(), } } } @@ -68,7 +72,7 @@ impl Default for SubPoolLimit { } /// Price bump config (in %) for the transaction pool underpriced check. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct PriceBumpConfig { /// Default price bump (in %) for the transaction pool underpriced check. pub default_price_bump: u128, @@ -76,6 +80,17 @@ pub struct PriceBumpConfig { pub replace_blob_tx_price_bump: u128, } +impl PriceBumpConfig { + /// Returns the price bump required to replace the given transaction type. + #[inline] + pub(crate) fn price_bump(&self, tx_type: u8) -> u128 { + if tx_type == EIP4844_TX_TYPE_ID { + return self.replace_blob_tx_price_bump + } + self.default_price_bump + } +} + impl Default for PriceBumpConfig { fn default() -> Self { Self { diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index 29a6fa9e9..1b49ea5d7 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -13,7 +13,8 @@ use crate::{ AddedPendingTransaction, AddedTransaction, OnNewCanonicalStateOutcome, }, traits::{BlockInfo, PoolSize}, - PoolConfig, PoolResult, PoolTransaction, TransactionOrdering, ValidPoolTransaction, U256, + PoolConfig, PoolResult, PoolTransaction, PriceBumpConfig, TransactionOrdering, + ValidPoolTransaction, U256, }; use fnv::FnvHashMap; use reth_primitives::{ @@ -99,7 +100,7 @@ impl TxPool { pending_pool: PendingPool::new(ordering), queued_pool: Default::default(), basefee_pool: Default::default(), - all_transactions: AllTransactions::new(config.max_account_slots), + all_transactions: AllTransactions::new(&config), config, metrics: Default::default(), } @@ -682,12 +683,18 @@ pub(crate) struct AllTransactions { last_seen_block_hash: H256, /// Expected base fee for the pending block. pending_basefee: u64, + /// Configured price bump settings for replacements + price_bumps: PriceBumpConfig, } impl AllTransactions { /// Create a new instance - fn new(max_account_slots: usize) -> Self { - Self { max_account_slots, ..Default::default() } + fn new(config: &PoolConfig) -> Self { + Self { + max_account_slots: config.max_account_slots, + price_bumps: config.price_bumps, + ..Default::default() + } } /// Returns an iterator over all _unique_ hashes in the pool @@ -1031,23 +1038,26 @@ impl AllTransactions { Ok(transaction) } - /// Returns true if `transaction_a` is underpriced compared to `transaction_B`. + /// Returns true if the replacement candidate is underpriced and can't replace the existing + /// transaction. fn is_underpriced( - transaction_a: &ValidPoolTransaction, - transaction_b: &ValidPoolTransaction, - price_bump: u128, + existing_transaction: &ValidPoolTransaction, + maybe_replacement: &ValidPoolTransaction, + price_bumps: &PriceBumpConfig, ) -> bool { - let tx_a_max_priority_fee_per_gas = - transaction_a.transaction.max_priority_fee_per_gas().unwrap_or(0); - let tx_b_max_priority_fee_per_gas = - transaction_b.transaction.max_priority_fee_per_gas().unwrap_or(0); + let price_bump = price_bumps.price_bump(existing_transaction.tx_type()); - transaction_a.max_fee_per_gas() <= - transaction_b.max_fee_per_gas() * (100 + price_bump) / 100 || - (tx_a_max_priority_fee_per_gas <= - tx_b_max_priority_fee_per_gas * (100 + price_bump) / 100 && - tx_a_max_priority_fee_per_gas != 0 && - tx_b_max_priority_fee_per_gas != 0) + let existing_max_priority_fee_per_gas = + maybe_replacement.transaction.max_priority_fee_per_gas().unwrap_or(0); + let replacement_max_priority_fee_per_gas = + existing_transaction.transaction.max_priority_fee_per_gas().unwrap_or(0); + + maybe_replacement.max_fee_per_gas() <= + existing_transaction.max_fee_per_gas() * (100 + price_bump) / 100 || + (existing_max_priority_fee_per_gas <= + replacement_max_priority_fee_per_gas * (100 + price_bump) / 100 && + existing_max_priority_fee_per_gas != 0 && + replacement_max_priority_fee_per_gas != 0) } /// Inserts a new transaction into the pool. @@ -1117,11 +1127,10 @@ impl AllTransactions { Entry::Occupied(mut entry) => { // Transaction already exists // Ensure the new transaction is not underpriced - if Self::is_underpriced( - transaction.as_ref(), entry.get().transaction.as_ref(), - PoolConfig::default().price_bump, + transaction.as_ref(), + &self.price_bumps, ) { return Err(InsertErr::Underpriced { transaction: pool_tx.transaction, @@ -1257,6 +1266,7 @@ impl Default for AllTransactions { last_seen_block_number: 0, last_seen_block_hash: Default::default(), pending_basefee: Default::default(), + price_bumps: Default::default(), } } }