Skip to content

Commit f6fcb85

Browse files
committed
feat: escalator queries onchain gas price
1 parent 8e090d7 commit f6fcb85

4 files changed

Lines changed: 111 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ethers-middleware/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ethers-signers = { version = "^1.0.0", path = "../ethers-signers", default-featu
2323

2424
async-trait = { version = "0.1.50", default-features = false }
2525
auto_impl = { version = "0.5.0", default-features = false }
26+
eyre = "0.6"
2627
serde = { version = "1.0.124", default-features = false, features = ["derive"] }
2728
thiserror = { version = "1.0", default-features = false }
2829
futures-util = { version = "^0.3" }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use ethers_providers::Middleware;
2+
use eyre::Result;
3+
4+
use ethers_core::{
5+
types::{BlockNumber, U256},
6+
utils::{
7+
eip1559_default_estimator, EIP1559_FEE_ESTIMATION_PAST_BLOCKS,
8+
EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE,
9+
},
10+
};
11+
12+
pub async fn estimate_eip1559_fees_default<M>(
13+
provider: &M,
14+
base_fee_per_gas: U256,
15+
) -> Result<(U256, U256, U256)>
16+
where
17+
M: Middleware,
18+
{
19+
let fee_history = provider
20+
.fee_history(
21+
EIP1559_FEE_ESTIMATION_PAST_BLOCKS,
22+
BlockNumber::Latest,
23+
&[EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE],
24+
)
25+
.await
26+
.map_err(|e| eyre::eyre!("Failed to fetch fee history: {}", e))?;
27+
28+
// use the provided fee estimator function, or fallback to the default implementation.
29+
let (max_fee_per_gas, max_priority_fee_per_gas) =
30+
eip1559_default_estimator(base_fee_per_gas, fee_history.reward);
31+
32+
Ok((base_fee_per_gas, max_fee_per_gas, max_priority_fee_per_gas))
33+
}

ethers-middleware/src/gas_escalator/mod.rs

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
mod estimate_fee;
2+
13
mod geometric;
4+
use estimate_fee::estimate_eip1559_fees_default;
25
pub use geometric::GeometricGasPrice;
36

47
mod linear;
@@ -13,7 +16,12 @@ use thiserror::Error;
1316
use tracing::{self, instrument};
1417
use tracing_futures::Instrument;
1518

16-
use ethers_core::types::{transaction::eip2718::TypedTransaction, BlockId, TxHash, H256, U256};
19+
use ethers_core::{
20+
types::{
21+
transaction::eip2718::TypedTransaction, Block, BlockId, BlockNumber, TxHash, H256, U256,
22+
},
23+
utils::parse_units,
24+
};
1725
use ethers_providers::{interval, FromErr, Middleware, PendingTransaction, StreamExt};
1826

1927
#[cfg(not(target_arch = "wasm32"))]
@@ -26,6 +34,9 @@ type WatcherFuture<'a> = Pin<Box<dyn futures_util::stream::Stream<Item = ()> + '
2634
#[cfg(not(target_arch = "wasm32"))]
2735
type WatcherFuture<'a> = Pin<Box<dyn futures_util::stream::Stream<Item = ()> + Send + 'a>>;
2836

37+
const GAS_PRICE_MULTIPLIER_NUMERATOR: u64 = 110;
38+
const GAS_PRICE_MULTIPLIER_DENOMINATOR: u64 = 100;
39+
2940
/// Trait for fetching updated gas prices after a transaction has been first
3041
/// broadcast
3142
pub trait GasEscalator: Send + Sync + std::fmt::Debug {
@@ -68,7 +79,12 @@ pub struct MonitoredTransaction {
6879
}
6980

7081
impl MonitoredTransaction {
71-
fn escalate_gas_price<E: GasEscalator>(&self, escalator: E) -> Option<TypedTransaction> {
82+
async fn escalate_gas_price<E: GasEscalator, M: Middleware>(
83+
&self,
84+
escalator: E,
85+
provider: &M,
86+
latest_block: Option<Block<TxHash>>,
87+
) -> Option<TypedTransaction> {
7288
// Get the new gas price based on how much time passed since the
7389
// tx was last broadcast
7490
let time_elapsed = self.creation_time.elapsed().as_secs();
@@ -77,7 +93,17 @@ impl MonitoredTransaction {
7793
let Some(gas_price) = tx.gas_price else {
7894
return None;
7995
};
80-
let new_gas_price = escalator.get_gas_price(gas_price, time_elapsed);
96+
// read current gas price from the provider
97+
// and multiply it by 1.1 to have some safety margin
98+
let current_network_gas_price =
99+
mulitply_gas_price(provider.get_gas_price().await.unwrap_or_default());
100+
let escalated_gas_price = escalator.get_gas_price(gas_price, time_elapsed);
101+
tracing::debug!(
102+
escalated_gas_price = ?escalated_gas_price,
103+
current_network_gas_price = ?current_network_gas_price,
104+
"comparing escalated gas price with current network gas price"
105+
);
106+
let new_gas_price = escalated_gas_price.max(current_network_gas_price);
81107
let mut updated_tx = tx.clone();
82108
updated_tx.gas_price = Some(new_gas_price);
83109
Some(updated_tx.into())
@@ -98,9 +124,42 @@ impl MonitoredTransaction {
98124
let Some(max_priority_fee_per_gas) = tx.max_priority_fee_per_gas else {
99125
return None;
100126
};
101-
let new_max_fee_per_gas = escalator.get_gas_price(max_fee_per_gas, time_elapsed);
102-
let new_max_priority_fee_per_gas =
127+
128+
let base_fee_per_gas =
129+
match latest_block.map(|block| block.base_fee_per_gas).flatten() {
130+
Some(base_fee_per_gas) => base_fee_per_gas,
131+
None => {
132+
tracing::warn!(
133+
"No base fee per gas found in latest block, defaulting to 50 gwei"
134+
);
135+
parse_units(50, 9).unwrap().into()
136+
}
137+
};
138+
// read current gas price from the provider
139+
// and multiply it by 1.1 to have some safety margin
140+
let (_, current_max_fee_per_gas, current_max_priority_fee_per_gas) =
141+
estimate_eip1559_fees_default(provider, base_fee_per_gas)
142+
.await
143+
.unwrap_or_default();
144+
let (multiplied_max_fee_per_gas, multiplied_max_priority_fee_per_gas) = (
145+
mulitply_gas_price(current_max_fee_per_gas),
146+
mulitply_gas_price(current_max_priority_fee_per_gas),
147+
);
148+
let escalated_max_fee_per_gas =
149+
escalator.get_gas_price(max_fee_per_gas, time_elapsed);
150+
let escalated_max_priority_fee_per_gas =
103151
escalator.get_gas_price(max_priority_fee_per_gas, time_elapsed);
152+
let new_max_fee_per_gas = escalated_max_fee_per_gas.max(multiplied_max_fee_per_gas);
153+
let new_max_priority_fee_per_gas =
154+
escalated_max_priority_fee_per_gas.max(multiplied_max_priority_fee_per_gas);
155+
156+
tracing::debug!(
157+
escalated_max_fee_per_gas = ?escalated_max_fee_per_gas,
158+
escalated_max_priority_fee_per_gas = ?escalated_max_priority_fee_per_gas,
159+
multiplied_max_fee_per_gas = ?multiplied_max_fee_per_gas,
160+
multiplied_max_priority_fee_per_gas = ?multiplied_max_priority_fee_per_gas,
161+
"comparing escalated gas price with current network gas price"
162+
);
104163
let mut updated_tx = tx.clone();
105164
updated_tx.max_fee_per_gas = Some(new_max_fee_per_gas);
106165
updated_tx.max_priority_fee_per_gas = Some(new_max_priority_fee_per_gas);
@@ -110,6 +169,12 @@ impl MonitoredTransaction {
110169
}
111170
}
112171

172+
fn mulitply_gas_price(gas_price: U256) -> U256 {
173+
let numerator = U256::from(GAS_PRICE_MULTIPLIER_NUMERATOR);
174+
let denominator = U256::from(GAS_PRICE_MULTIPLIER_DENOMINATOR);
175+
gas_price * numerator / denominator
176+
}
177+
113178
/// A Gas escalator allows bumping transactions' gas price to avoid getting them
114179
/// stuck in the memory pool.
115180
///
@@ -366,6 +431,8 @@ impl<M, E: Clone> EscalationTask<M, E> {
366431
tracing::trace!(?monitored_txs, "In the escalator watcher loop. Monitoring txs");
367432
}
368433
let mut new_txs_to_monitor = vec![];
434+
let maybe_latest_block =
435+
self.inner.get_block(BlockId::Number(BlockNumber::Latest)).await.ok().flatten();
369436
for old_monitored_tx in monitored_txs {
370437
let receipt = if let Some(tx_hash) = old_monitored_tx.hash {
371438
tracing::trace!(tx_hash = ?old_monitored_tx.hash, "checking if exists");
@@ -382,7 +449,10 @@ impl<M, E: Clone> EscalationTask<M, E> {
382449
tracing::debug!(tx = ?receipt.transaction_hash, "Transaction was included onchain, dropping from escalator");
383450
continue;
384451
}
385-
let Some(new_tx) = old_monitored_tx.escalate_gas_price(self.escalator.clone()) else {
452+
let Some(new_tx) = old_monitored_tx
453+
.escalate_gas_price(self.escalator.clone(), &self.inner, maybe_latest_block.clone())
454+
.await
455+
else {
386456
tracing::error!(tx=?old_monitored_tx.hash, "gas price is not set for transaction, dropping from escalator");
387457
continue;
388458
};

0 commit comments

Comments
 (0)