fix(txpool): use ceiling division for replacement tx price bump check (#23012)

Signed-off-by: Delweng <delweng@gmail.com>
This commit is contained in:
Delweng
2026-03-14 15:56:10 +08:00
committed by GitHub
parent 93cb8934ea
commit d3d7fb31d7
2 changed files with 34 additions and 4 deletions

View File

@@ -3104,6 +3104,35 @@ mod tests {
assert_eq!(pool.len(), 1);
}
#[test]
fn insert_replace_underpriced_rounds_up_minimum_bump() {
let on_chain_balance = U256::ZERO;
let on_chain_nonce = 0;
let mut f = MockTransactionFactory::default();
let mut pool = AllTransactions { minimal_protocol_basefee: 0, ..Default::default() };
let mut tx = MockTransaction::eip1559().inc_price().inc_limit();
tx.set_priority_fee(1);
tx.set_max_fee(1);
let first = f.validated(tx.clone());
let _ = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce).unwrap();
let mut replacement = f.validated(tx.rng_hash().inc_price());
replacement.transaction.set_priority_fee(1);
replacement.transaction.set_max_fee(2);
let err =
pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap_err();
assert!(matches!(err, InsertErr::Underpriced { .. }));
assert!(pool.contains(first.hash()));
assert_eq!(pool.len(), 1);
replacement.transaction.set_priority_fee(2);
replacement.transaction.set_max_fee(2);
let replaced = pool.insert_tx(replacement, on_chain_balance, on_chain_nonce).unwrap();
assert!(replaced.replaced_tx.is_some());
assert_eq!(pool.len(), 1);
}
#[test]
fn insert_conflicting_type_normal_to_blob() {
let on_chain_balance = U256::from(10_000);

View File

@@ -455,9 +455,11 @@ impl<T: PoolTransaction> ValidPoolTransaction<T> {
//
// The bump is different for EIP-4844 and other transactions. See `PriceBumpConfig`.
let price_bump = price_bumps.price_bump(self.tx_type());
let required_bumped_fee =
|existing_fee: u128| existing_fee.saturating_mul(100 + price_bump).div_ceil(100);
// Check if the max fee per gas is underpriced.
if maybe_replacement.max_fee_per_gas() < self.max_fee_per_gas() * (100 + price_bump) / 100 {
if maybe_replacement.max_fee_per_gas() < required_bumped_fee(self.max_fee_per_gas()) {
return true
}
@@ -470,7 +472,7 @@ impl<T: PoolTransaction> ValidPoolTransaction<T> {
if existing_max_priority_fee_per_gas != 0 &&
replacement_max_priority_fee_per_gas != 0 &&
replacement_max_priority_fee_per_gas <
existing_max_priority_fee_per_gas * (100 + price_bump) / 100
required_bumped_fee(existing_max_priority_fee_per_gas)
{
return true
}
@@ -480,8 +482,7 @@ impl<T: PoolTransaction> ValidPoolTransaction<T> {
// This enforces that blob txs can only be replaced by blob txs
let replacement_max_blob_fee_per_gas =
maybe_replacement.transaction.max_fee_per_blob_gas().unwrap_or_default();
if replacement_max_blob_fee_per_gas <
existing_max_blob_fee_per_gas * (100 + price_bump) / 100
if replacement_max_blob_fee_per_gas < required_bumped_fee(existing_max_blob_fee_per_gas)
{
return true
}