forked from dojoengine/dojo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: progress on issue dojoengine#2558 gas oracle feature on katana
- Loading branch information
1 parent
f6659db
commit 8e316be
Showing
5 changed files
with
233 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,187 @@ | ||
use std::collections::VecDeque; | ||
use std::fmt::Debug; | ||
use std::future::IntoFuture; | ||
use std::pin::Pin; | ||
|
||
use alloy_provider::{Provider, ProviderBuilder}; | ||
use alloy_rpc_types_eth::BlockNumberOrTag; | ||
use alloy_transport::Transport; | ||
use anyhow::{Context, Ok}; | ||
use futures::Future; | ||
use katana_primitives::block::GasPrices; | ||
use tokio::time::Duration; | ||
use url::Url; | ||
|
||
const BUFFER_SIZE: usize = 60; | ||
const INTERVAL: Duration = Duration::from_secs(60); | ||
const ONE_GWEI: u128 = 1_000_000_000; | ||
|
||
// TODO: implement a proper gas oracle function - sample the l1 gas and data gas prices | ||
// currently this just return the hardcoded value set from the cli or if not set, the default value. | ||
#[derive(Debug)] | ||
pub struct L1GasOracle { | ||
pub enum L1GasOracle { | ||
Fixed(FixedL1GasOracle), | ||
Sampled(SampledL1GasOracle), | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct FixedL1GasOracle { | ||
gas_prices: GasPrices, | ||
data_gas_prices: GasPrices, | ||
} | ||
|
||
#[derive(Debug, Default)] | ||
pub struct SampledL1GasOracle { | ||
gas_prices: GasPrices, | ||
data_gas_prices: GasPrices, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct GasOracleWorker { | ||
pub l1_oracle: SampledL1GasOracle, | ||
pub l1_provider_url: Option<Url>, | ||
} | ||
|
||
impl L1GasOracle { | ||
pub fn fixed(gas_prices: GasPrices, data_gas_prices: GasPrices) -> Self { | ||
Self { gas_prices, data_gas_prices } | ||
L1GasOracle::Fixed(FixedL1GasOracle { gas_prices, data_gas_prices }) | ||
} | ||
|
||
pub fn sampled() -> Self { | ||
L1GasOracle::Sampled(SampledL1GasOracle { | ||
gas_prices: GasPrices::default(), | ||
data_gas_prices: GasPrices::default(), | ||
}) | ||
} | ||
|
||
/// Returns the current gas prices. | ||
pub fn current_gas_prices(&self) -> GasPrices { | ||
self.gas_prices.clone() | ||
match self { | ||
L1GasOracle::Fixed(fixed) => fixed.gas_prices.clone(), | ||
L1GasOracle::Sampled(sampled) => sampled.gas_prices.clone(), | ||
} | ||
} | ||
|
||
/// Returns the current data gas prices. | ||
pub fn current_data_gas_prices(&self) -> GasPrices { | ||
match self { | ||
L1GasOracle::Fixed(fixed) => fixed.data_gas_prices.clone(), | ||
L1GasOracle::Sampled(sampled) => sampled.data_gas_prices.clone(), | ||
} | ||
} | ||
} | ||
|
||
impl SampledL1GasOracle { | ||
pub fn current_data_gas_prices(&self) -> GasPrices { | ||
self.data_gas_prices.clone() | ||
} | ||
|
||
pub fn current_gas_prices(&self) -> GasPrices { | ||
self.gas_prices.clone() | ||
} | ||
} | ||
|
||
impl FixedL1GasOracle { | ||
pub fn current_data_gas_prices(&self) -> GasPrices { | ||
self.data_gas_prices.clone() | ||
} | ||
|
||
pub fn current_gas_prices(&self) -> GasPrices { | ||
self.gas_prices.clone() | ||
} | ||
} | ||
|
||
async fn update_gas_price<P: Provider<T>, T: Transport + Clone>( | ||
l1_oracle: &mut SampledL1GasOracle, | ||
provider: P, | ||
buffer: &mut GasPriceBuffer, | ||
) -> anyhow::Result<()> { | ||
// Attempt to get the gas price from L1 | ||
let last_block_number = provider.get_block_number().await?; | ||
let fee_history = | ||
provider.get_fee_history(1, BlockNumberOrTag::Number(last_block_number), &[]).await?; | ||
|
||
let latest_gas_price = fee_history.base_fee_per_gas.last().context("Getting eth gas price")?; | ||
buffer.add_sample(*latest_gas_price); | ||
|
||
let blob_fee_history = fee_history.base_fee_per_blob_gas; | ||
let avg_blob_base_fee = blob_fee_history.iter().last().context("Getting blob gas price")?; | ||
|
||
let avg_blob_fee_eth = *avg_blob_base_fee; | ||
let avg_blob_fee_strk = *avg_blob_base_fee + ONE_GWEI; | ||
|
||
let avg_gas_price = GasPrices { | ||
eth: buffer.average(), | ||
// The price of gas on Starknet is set to the average of the last 60 gas price samples, plus | ||
// 1 gwei. | ||
strk: buffer.average() + ONE_GWEI, | ||
}; | ||
let avg_blob_price = GasPrices { eth: avg_blob_fee_eth, strk: avg_blob_fee_strk }; | ||
|
||
l1_oracle.gas_prices = avg_gas_price; | ||
l1_oracle.data_gas_prices = avg_blob_price; | ||
Ok(()) | ||
} | ||
|
||
impl GasOracleWorker { | ||
pub fn new(l1_provider_url: Option<Url>) -> Self { | ||
Self { l1_oracle: SampledL1GasOracle::default(), l1_provider_url } | ||
} | ||
|
||
pub async fn run(&mut self) -> anyhow::Result<()> { | ||
let mut buffer = GasPriceBuffer::new(); | ||
let provider = ProviderBuilder::new() | ||
.on_http(self.l1_provider_url.clone().expect("gas_oracle.rs #133")); | ||
// every 60 seconds, Starknet samples the base price of gas and data gas on L1 | ||
let mut interval = tokio::time::interval(INTERVAL); | ||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); | ||
|
||
loop { | ||
tokio::select! { | ||
// tick every 60 seconds | ||
_ = interval.tick() => { | ||
if let Err(e) = update_gas_price(&mut self.l1_oracle, provider.clone(), &mut buffer).await { | ||
eprintln!("Error updating gas price: {}", e); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl IntoFuture for GasOracleWorker { | ||
type Output = anyhow::Result<()>; | ||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>; | ||
|
||
fn into_future(mut self) -> Self::IntoFuture { | ||
Box::pin(async move { self.run().await }) | ||
} | ||
} | ||
|
||
// Buffer to store the last 60 gas price samples | ||
#[derive(Debug)] | ||
pub struct GasPriceBuffer { | ||
buffer: VecDeque<u128>, | ||
} | ||
|
||
impl GasPriceBuffer { | ||
fn new() -> Self { | ||
Self { buffer: VecDeque::with_capacity(BUFFER_SIZE) } | ||
} | ||
|
||
fn add_sample(&mut self, sample: u128) { | ||
if self.buffer.len() == BUFFER_SIZE { | ||
// remove oldest sample if buffer is full | ||
self.buffer.pop_front(); | ||
} | ||
self.buffer.push_back(sample); | ||
} | ||
|
||
fn average(&self) -> u128 { | ||
if self.buffer.is_empty() { | ||
return 0; | ||
} | ||
let sum: u128 = self.buffer.iter().sum(); | ||
sum / self.buffer.len() as u128 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters