Skip to content

Commit

Permalink
feat(starknet_l1_provider): add the L1GasPriceProvider implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
guy-starkware committed Feb 5, 2025
1 parent ece1129 commit 02ab392
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 8 deletions.
4 changes: 0 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions crates/starknet_l1_gas_price/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,11 @@ repository.workspace = true
license.workspace = true

[dependencies]
async-trait.workspace = true
papyrus_base_layer.workspace = true
papyrus_config.workspace = true
serde.workspace = true
starknet_api.workspace = true
starknet_sequencer_infra.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
validator.workspace = true

[lints]
Expand Down
94 changes: 94 additions & 0 deletions crates/starknet_l1_gas_price/src/l1_gas_price_provider.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
use std::collections::{BTreeMap, VecDeque};

use papyrus_config::dumping::{ser_param, SerializeConfig};
use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam};
use serde::{Deserialize, Serialize};
use starknet_api::block::{BlockNumber, BlockTimestamp};
use thiserror::Error;
use validator::Validate;

// TODO(guyn): both these constants need to go into VersionedConstants.
pub const MEAN_NUMBER_OF_BLOCKS: u64 = 300;
pub const LAG_MARGIN_SECONDS: u64 = 60;

#[cfg(test)]
#[path = "l1_gas_price_provider_tests.rs"]
pub mod l1_gas_price_provider_tests;

// TODO(guyn, Gilad): consider moving this to starknet_l1_provider_types/lib.rs?
// This is an interface that allows sharing the provider with the scraper across threads.
pub trait L1GasPriceProviderClient: Send + Sync {
Expand All @@ -29,3 +39,87 @@ pub enum L1GasPriceProviderError {
#[error("Failed to get price info: {0}")]
GetPriceInfoError(String),
}

#[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq)]
pub struct L1GasPriceProviderConfig {
pub storage_limit: usize,
}

impl Default for L1GasPriceProviderConfig {
fn default() -> Self {
Self { storage_limit: usize::try_from(10 * MEAN_NUMBER_OF_BLOCKS).unwrap() }
}
}

impl SerializeConfig for L1GasPriceProviderConfig {
fn dump(&self) -> BTreeMap<ParamPath, SerializedParam> {
BTreeMap::from([ser_param(
"storage_limit",
&self.storage_limit,
"The maximum number of blocks to store in the gas price provider's buffer.",
ParamPrivacyInput::Public,
)])
}
}

#[derive(Clone, Debug, Default)]
pub struct L1GasPriceProvider {
config: L1GasPriceProviderConfig,
data: VecDeque<(BlockNumber, BlockTimestamp, u128, u128)>,
}

// TODO(guyn): remove the dead code attribute when we use this.
#[allow(dead_code)]
impl L1GasPriceProvider {
pub fn new(config: L1GasPriceProviderConfig) -> Self {
Self { config, data: VecDeque::new() }
}

pub fn add_price_info(
&mut self,
height: BlockNumber,
timestamp: BlockTimestamp,
gas_price: u128,
data_gas_price: u128,
) -> Result<(), L1GasPriceProviderError> {
self.data.push_back((height, timestamp, gas_price, data_gas_price));
if self.data.len() >= self.config.storage_limit {
self.data.pop_front();
}
Ok(())
}

pub fn get_price_info(
&mut self,
timestamp: BlockTimestamp,
) -> Result<(u128, u128), L1GasPriceProviderError> {
let mut gas_price = 0;
let mut data_gas_price = 0;

let index_after_timestamp =
self.data.iter().position(|(_, ts, _, _)| ts.0 >= timestamp.0 - LAG_MARGIN_SECONDS);
if let Some(index) = index_after_timestamp {
let number = usize::try_from(MEAN_NUMBER_OF_BLOCKS).unwrap();
if index < number {
return Err(L1GasPriceProviderError::GetPriceInfoError(format!(
"Insufficient number of block prices were cached ({})",
index
)));
}
// Go over all elements between index-number to index.
for (_height, _ts, gp, dgp) in self.data.iter().skip(index - number + 1).take(number) {
gas_price += gp;
data_gas_price += dgp;
}
Ok((
gas_price / u128::from(MEAN_NUMBER_OF_BLOCKS),
data_gas_price / u128::from(MEAN_NUMBER_OF_BLOCKS),
))
} else {
Err(L1GasPriceProviderError::GetPriceInfoError(format!(
"No block price data from time {} - {}s",
timestamp.0, LAG_MARGIN_SECONDS
)))
}
}
}
69 changes: 69 additions & 0 deletions crates/starknet_l1_gas_price/src/l1_gas_price_provider_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use starknet_api::block::{BlockNumber, BlockTimestamp};

use crate::l1_gas_price_provider::{L1GasPriceProvider, LAG_MARGIN_SECONDS, MEAN_NUMBER_OF_BLOCKS};

#[tokio::test]
async fn gas_price_provider_mean_prices() {
let data_gas_price_const = 42;
let num_added_blocks = 10;
let mut provider = L1GasPriceProvider::default();
let num_blocks = MEAN_NUMBER_OF_BLOCKS * 2;
let final_timestamp = num_blocks * 2;
for i in 0..=num_blocks {
provider
.add_price_info(BlockNumber(i), BlockTimestamp(i * 2), i.into(), data_gas_price_const)
.unwrap();
}

// This calculation will grap all data from final_timestamp - MEAN_NUMBER_OF_BLOCKS to
// final_timestamp.
let (gas_price, data_gas_price) =
provider.get_price_info(BlockTimestamp(final_timestamp + LAG_MARGIN_SECONDS)).unwrap();
// The gas prices (set arbitrarily to equal the block number) should go from 300 to 600 in this
// range. So the mean is 450.
let gas_price_calculation =
(MEAN_NUMBER_OF_BLOCKS + 1..=num_blocks).sum::<u64>() / MEAN_NUMBER_OF_BLOCKS;
// The data gas price is set to a const, so this should be reflected in the result.
assert_eq!(gas_price, gas_price_calculation.into());
assert_eq!(data_gas_price, data_gas_price_const);

// Add a few more blocks to the provider.
for i in 1..num_added_blocks {
provider
.add_price_info(
BlockNumber(num_blocks + i),
BlockTimestamp(final_timestamp + i * 2),
(num_blocks + i).into(),
data_gas_price_const,
)
.unwrap();
}

// This should not change the results if we as for the same timestamp.
let (gas_price, data_gas_price) =
provider.get_price_info(BlockTimestamp(final_timestamp + LAG_MARGIN_SECONDS)).unwrap();
assert_eq!(gas_price, gas_price_calculation.into());
assert_eq!(data_gas_price, data_gas_price_const);

// But if we take a slightly higher timestamp the gas price should go up.
// Data gas price remains constant.
let (gas_price, data_gas_price) =
provider.get_price_info(BlockTimestamp(final_timestamp + LAG_MARGIN_SECONDS + 5)).unwrap();
assert!(gas_price > gas_price_calculation.into());
assert_eq!(data_gas_price, data_gas_price_const);

// If we add a very high data gas price sample, it should increase the mean data gas price.
provider
.add_price_info(
BlockNumber(num_blocks + num_added_blocks),
BlockTimestamp(final_timestamp + num_added_blocks * 2),
(num_blocks + num_added_blocks).into(),
data_gas_price_const * 100,
)
.unwrap();

let (_, data_gas_price) = provider
.get_price_info(BlockTimestamp(final_timestamp + num_added_blocks * 2 + LAG_MARGIN_SECONDS))
.unwrap();
assert!(data_gas_price > data_gas_price_const);
}

0 comments on commit 02ab392

Please sign in to comment.