Skip to content

Commit

Permalink
WIP: progress on issue dojoengine#2558 gas oracle feature on katana
Browse files Browse the repository at this point in the history
  • Loading branch information
augustin-v committed Nov 16, 2024
1 parent f6659db commit 8e316be
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 15 deletions.
38 changes: 34 additions & 4 deletions crates/katana/cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use std::collections::HashSet;
use std::path::PathBuf;

use alloy_primitives::U256;
use anyhow::{Context, Result};
use anyhow::{Context, Ok, Result};
use clap::Parser;
use katana_core::constants::DEFAULT_SEQUENCER_ADDRESS;
use katana_core::service::messaging::MessagingConfig;
use katana_node::config::db::DbConfig;
use katana_node::config::dev::{DevConfig, FixedL1GasPriceConfig};
use katana_node::config::dev::{DevConfig, FixedL1GasPriceConfig, GasPriceWorkerConfig};
use katana_node::config::execution::ExecutionConfig;
use katana_node::config::fork::ForkingConfig;
use katana_node::config::metrics::MetricsConfig;
Expand All @@ -22,6 +22,7 @@ use serde::{Deserialize, Serialize};
use tracing::{info, Subscriber};
use tracing_log::LogTracer;
use tracing_subscriber::{fmt, EnvFilter};
use url::Url;

use crate::file::NodeArgsConfig;
use crate::options::*;
Expand Down Expand Up @@ -68,6 +69,15 @@ pub struct NodeArgs {
#[arg(value_parser = katana_core::service::messaging::MessagingConfig::parse)]
pub messaging: Option<MessagingConfig>,

#[arg(long)]
#[arg(conflicts_with = "l1_provider_url")]
#[arg(help = "Disable L1 gas sampling and use hardcoded values.")]
pub no_sampling: bool,

#[arg(long = "l1.provider", value_name = "URL", alias = "l1-provider")]
#[arg(help = "The Ethereum RPC provider to sample the gas prices from.")]
pub l1_provider_url: Option<Url>,

#[command(flatten)]
pub logging: LoggingOptions,

Expand Down Expand Up @@ -170,8 +180,20 @@ impl NodeArgs {
let execution = self.execution_config();
let sequencing = self.sequencer_config();
let messaging = self.messaging.clone();

Ok(Config { metrics, db, dev, rpc, chain, execution, sequencing, messaging, forking })
let gas_price_worker = self.gas_price_worker_config();

Ok(Config {
metrics,
db,
dev,
rpc,
chain,
execution,
sequencing,
messaging,
forking,
gas_price_worker,
})
}

fn sequencer_config(&self) -> SequencingConfig {
Expand Down Expand Up @@ -258,6 +280,7 @@ impl NodeArgs {
fixed_gas_prices,
fee: !self.development.no_fee,
account_validation: !self.development.no_account_validation,
l1_worker: self.gas_price_worker_config(),
}
}

Expand Down Expand Up @@ -357,6 +380,13 @@ impl NodeArgs {

Ok(self)
}

fn gas_price_worker_config(&self) -> Option<GasPriceWorkerConfig> {
self.l1_provider_url.clone().map(|url| GasPriceWorkerConfig {
l1_provider_url: Some(url),
no_sampling: self.no_sampling,
})
}
}

#[cfg(test)]
Expand Down
168 changes: 165 additions & 3 deletions crates/katana/core/src/backend/gas_oracle.rs
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
}
}
4 changes: 3 additions & 1 deletion crates/katana/core/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use katana_trie::compute_merkle_root;
use parking_lot::RwLock;
use starknet::macros::short_string;
use starknet_types_core::hash::{self, StarkHash};

use tracing::info;

pub mod contract;
Expand Down Expand Up @@ -117,6 +118,7 @@ impl<EF: ExecutorFactory> Backend<EF> {
pub fn update_block_gas_prices(&self, block_env: &mut BlockEnv) {
block_env.l1_gas_prices = self.gas_oracle.current_gas_prices();
block_env.l1_data_gas_prices = self.gas_oracle.current_data_gas_prices();

}

pub fn mine_empty_block(
Expand Down Expand Up @@ -278,4 +280,4 @@ where
class_trie_root,
])
}
}
}
5 changes: 4 additions & 1 deletion crates/katana/node/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub mod metrics;
pub mod rpc;

use db::DbConfig;
use dev::DevConfig;
use dev::{DevConfig, GasPriceWorkerConfig};
use execution::ExecutionConfig;
use fork::ForkingConfig;
use katana_core::service::messaging::MessagingConfig;
Expand Down Expand Up @@ -45,6 +45,9 @@ pub struct Config {

/// Development options.
pub dev: DevConfig,

/// Gas L1 sampling options.
pub gas_price_worker: Option<GasPriceWorkerConfig>,
}

/// Configurations related to block production.
Expand Down
33 changes: 27 additions & 6 deletions crates/katana/node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use config::dev::GasPriceWorkerConfig;
use config::metrics::MetricsConfig;
use config::rpc::{ApiKind, RpcConfig};
use config::{Config, SequencingConfig};
Expand All @@ -19,7 +20,7 @@ use hyper::{Method, Uri};
use jsonrpsee::server::middleware::proxy_get_request::ProxyGetRequestLayer;
use jsonrpsee::server::{AllowHosts, ServerBuilder, ServerHandle};
use jsonrpsee::RpcModule;
use katana_core::backend::gas_oracle::L1GasOracle;
use katana_core::backend::gas_oracle::{GasOracleWorker, L1GasOracle};
use katana_core::backend::storage::Blockchain;
use katana_core::backend::Backend;
use katana_core::constants::{
Expand Down Expand Up @@ -94,6 +95,7 @@ pub struct Node {
pub metrics_config: Option<MetricsConfig>,
pub sequencing_config: SequencingConfig,
pub messaging_config: Option<MessagingConfig>,
pub gas_price_worker_config: Option<GasPriceWorkerConfig>,
forked_client: Option<ForkedClient>,
}

Expand Down Expand Up @@ -151,6 +153,22 @@ impl Node {
let node_components = (pool, backend, block_producer, validator, self.forked_client.take());
let rpc = spawn(node_components, self.rpc_config.clone()).await?;

// --- build and start the gas oracle worker task

// if the Option<GasWorkerConfig> is none, default to no sampling
if !self.gas_price_worker_config.as_ref().map_or(true, |config| config.no_sampling) {
let gas_oracle: GasOracleWorker = GasOracleWorker::new(
self.gas_price_worker_config.clone().expect("lib.src #160").l1_provider_url,
);

self.task_manager
.task_spawner()
.build_task()
.graceful_shutdown()
.name("L1 Gas oracle worker")
.spawn(async move { gas_oracle.into_future().await });
}

Ok(LaunchedNode { node: self, rpc })
}
}
Expand Down Expand Up @@ -206,18 +224,20 @@ pub async fn build(mut config: Config) -> Result<Node> {
// --- build l1 gas oracle

// Check if the user specify a fixed gas price in the dev config.
// cases to cover:
// 1. Fixed price by user
// 2. No fixed price by user and no sampling
// 3. Sampling with user input provider url
let gas_oracle = if let Some(fixed_prices) = config.dev.fixed_gas_prices {
L1GasOracle::fixed(fixed_prices.gas_price, fixed_prices.data_gas_price)
}
// TODO: for now we just use the default gas prices, but this should be a proper oracle in the
// future that can perform actual sampling.
else {
} else if config.gas_price_worker.as_ref().map_or(false, |worker| worker.no_sampling) {
L1GasOracle::fixed(
GasPrices { eth: DEFAULT_ETH_L1_GAS_PRICE, strk: DEFAULT_STRK_L1_GAS_PRICE },
GasPrices { eth: DEFAULT_ETH_L1_DATA_GAS_PRICE, strk: DEFAULT_STRK_L1_DATA_GAS_PRICE },
)
} else {
L1GasOracle::sampled()
};

let block_context_generator = BlockContextGenerator::default().into();
let backend = Arc::new(Backend {
gas_oracle,
Expand Down Expand Up @@ -255,6 +275,7 @@ pub async fn build(mut config: Config) -> Result<Node> {
messaging_config: config.messaging,
sequencing_config: config.sequencing,
task_manager: TaskManager::current(),
gas_price_worker_config: config.gas_price_worker,
};

Ok(node)
Expand Down

0 comments on commit 8e316be

Please sign in to comment.