Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(starknet_l1_provider): add L1 gas price scraper #4000

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

guy-starkware
Copy link
Contributor

No description provided.

@reviewable-StarkWare
Copy link

This change is Reviewable

@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch from 4a788c1 to 7550795 Compare February 6, 2025 12:05
@guy-starkware guy-starkware force-pushed the guyn/l1price/provider_impl branch from 6c8d247 to cf06a20 Compare February 6, 2025 12:09
@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch 2 times, most recently from 1e305a3 to d8a8aa2 Compare February 6, 2025 12:23
@guy-starkware guy-starkware force-pushed the guyn/l1price/provider_impl branch from cf06a20 to 52f16b4 Compare February 6, 2025 13:02
@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch from d8a8aa2 to c60e91f Compare February 6, 2025 14:13
@guy-starkware guy-starkware changed the title feat(fee): add L1 gas price scraper feat(starknet_l1_provider): add L1 gas price scraper Feb 9, 2025
Copy link

github-actions bot commented Feb 9, 2025

Benchmark movements:
tree_computation_flow performance improved 😺
tree_computation_flow time: [34.792 ms 34.819 ms 34.852 ms]
change: [-4.1032% -2.6019% -1.3067%] (p = 0.00 < 0.05)
Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
2 (2.00%) high mild
2 (2.00%) high severe

@guy-starkware guy-starkware changed the base branch from guyn/l1price/provider_impl to graphite-base/4000 February 9, 2025 13:46
@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch 2 times, most recently from 03bee00 to 88a3d8b Compare February 9, 2025 14:24
@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch from 88a3d8b to ecd9d6c Compare February 9, 2025 15:28
@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch from ecd9d6c to 7a210fb Compare February 10, 2025 09:58
Copy link

Benchmark movements:
tree_computation_flow performance improved 😺
tree_computation_flow time: [34.893 ms 34.953 ms 35.019 ms]
change: [-6.9114% -4.6523% -2.6302%] (p = 0.00 < 0.05)
Performance has improved.
Found 16 outliers among 100 measurements (16.00%)
11 (11.00%) high mild
5 (5.00%) high severe

@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch 2 times, most recently from dfa1220 to ed48308 Compare February 11, 2025 11:33
@guy-starkware guy-starkware changed the base branch from graphite-base/4000 to main February 11, 2025 11:33
Copy link
Contributor

@asmaastarkware asmaastarkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 8 files at r1, 2 of 8 files at r2, 3 of 3 files at r3, all commit messages.
Reviewable status: 6 of 9 files reviewed, 6 unresolved discussions


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 90 at r3 (raw file):

=


crates/starknet_l1_gas_price/src/l1_gas_price_scraper_test.rs line 56 at r3 (raw file):

#[async_trait]
impl BaseLayerContract for FakeBaseLayerContract {

why didn't u use automock?


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 85 at r3 (raw file):

        let latest_l1_block_number = self
            .base_layer
            .latest_l1_block_number(self.config.finality)

@matan-starkware what does it mean finality here (Ethereum)?
finality in consensus means after the commit stage?


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 89 at r3 (raw file):

            .map_err(L1GasPriceScraperError::BaseLayerError)?;

        if let Some(latest_l1_block_number) = latest_l1_block_number {

use let else


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 113 at r3 (raw file):

                        .map_err(L1GasPriceScraperError::GasPriceProviderError)?;

                    self.next_block_number_to_fetch = latest_l1_block_number + 1;

good catch to update it here (in case of failure we know where to resume)
u mean block_number?


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 140 at r3 (raw file):

    // Leaky abstraction, these errors should not propagate here.
    #[error(transparent)]
    NetworkError(ClientError),

if it's unused, add it when needed

Copy link
Contributor Author

@guy-starkware guy-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 6 of 9 files reviewed, 6 unresolved discussions (waiting on @asmaastarkware)


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 89 at r3 (raw file):

Previously, asmaastarkware (asmaa-starkware) wrote…

use let else

Done.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 90 at r3 (raw file):

Previously, asmaastarkware (asmaa-starkware) wrote…

=

I think this does not include =

If next_block_number_to_fetch is 10 and latest_l1_block_number is also 10, that means we need to fetch that block. If we use >= instead of > we will not get that.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 113 at r3 (raw file):

Previously, asmaastarkware (asmaa-starkware) wrote…

good catch to update it here (in case of failure we know where to resume)
u mean block_number?

Yes, thank you!


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 140 at r3 (raw file):

Previously, asmaastarkware (asmaa-starkware) wrote…

if it's unused, add it when needed

Yes, I'll move it to the next PR.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper_test.rs line 56 at r3 (raw file):

Previously, asmaastarkware (asmaa-starkware) wrote…

why didn't u use automock?

I tried, it was more complicated for some reason.

@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch 2 times, most recently from 96e4647 to 4c696c9 Compare February 11, 2025 13:18
Copy link
Contributor

@matan-starkware matan-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 4 files at r5, all commit messages.
Reviewable status: 4 of 9 files reviewed, 10 unresolved discussions (waiting on @asmaastarkware)


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 85 at r3 (raw file):

Previously, asmaastarkware (asmaa-starkware) wrote…

@matan-starkware what does it mean finality here (Ethereum)?
finality in consensus means after the commit stage?

Finality is an ambiguous term that refers to when we are confident that a block will not be reverted.

In the strict sense it means that over 2/3 of validators would need to fork the system (in most PoS systems). In Tendermint this happens immediately (e.g. as soon as H is decided). For Ethereum this means basically that 2 epochs (64 blocks) have passed. This though takes 12m and we don't want to wait that long due to stale data.

In practice people use much smaller numbers of blocks for finality; an acceptable probability that the block will not reorg. If I remember correctly, Ethereum switch to PoS about 2.5 years ago and has never had a reorg longer than 2 blocks. So we use 3 blocks as our finality, as this is only 30s of lag from the tip of Ethereum and has good enough likelihood to be stable. It is though configurable.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper_test.rs line 56 at r3 (raw file):

Previously, guy-starkware wrote…

I tried, it was more complicated for some reason.

It is complicated for 2 reasons:

  1. Associated types - can get this to work by following the automock documentation. I made a new error type for the example below
  2. Parameters with lifetimes - this one seems to be a known issue which unfortunately requires changing the function definition, but luckily seems to be just a boilerplate change, nothing too complex to implement. Can't mock trait containing function that takes an argument of type Option<&T> asomers/mockall#61

I lean towards this being worthwhile, but let's check with Gilad what he thinks about making this change.

#[derive(thiserror::Error, Debug)]
pub enum MockError {}

/// Interface for getting data from the Starknet base contract.
#[cfg_attr(test, automock(type Error = MockError;))]
#[async_trait]
pub trait BaseLayerContract {
    type Error: Error + Display + Debug;

    /// Get specific events from the Starknet base contract between two L1 block numbers.
    async fn events<'a>(
        &'a self,
        block_range: RangeInclusive<L1BlockNumber>,
        event_identifiers: &'a [&'a str],
    ) -> Result<Vec<L1Event>, Self::Error>;

}

crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 23 at r5 (raw file):

type L1GasPriceScraperResult<T, B> = Result<T, L1GasPriceScraperError<B>>;
pub type SharedL1GasPriceProvider = Arc<dyn L1GasPriceProviderClient>;

This should get moved into the infra "types" crate when we make that

Code quote:

pub type SharedL1GasPriceProvider = Arc<dyn L1GasPriceProviderClient>;

crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 27 at r5 (raw file):

// TODO(guyn): find a way to synchronize the value of number_of_blocks_for_mean
// with the one in L1GasPriceProviderConfig. In the end they should not be config
// items but values drawn from VersionedConstants.

Just to be clear, from the perspective of the scraper these will remain in the config, it has no idea about versioned constants. From the global perspective, yes the source of these values is versioned constants.

Code quote:

// TODO(guyn): find a way to synchronize the value of number_of_blocks_for_mean
// with the one in L1GasPriceProviderConfig. In the end they should not be config
// items but values drawn from VersionedConstants.

crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 46 at r5 (raw file):

            finality: 0,
            polling_interval: Duration::from_secs(1),
            number_of_blocks_for_mean: 300,

Why not copy the defaults from Gilad's L1ScraperConfig?

Code quote:

            l1_block_to_start_scraping_from: 0,
            chain_id: ChainId::Mainnet,
            finality: 0,
            polling_interval: Duration::from_secs(1),
            number_of_blocks_for_mean: 300,

crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 132 at r5 (raw file):

    #[error(transparent)]
    NetworkError(ClientError),
}

Move this above the impls (I recommend putting this above the structs which have impls also)

https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/style.md#order-of-items

Code quote:

#[derive(Error, Debug)]
pub enum L1GasPriceScraperError<T: BaseLayerContract + Send + Sync> {
    #[error("Base layer error: {0}")]
    BaseLayerError(T::Error),
    #[error("Could not update gas price provider: {0}")]
    GasPriceProviderError(L1GasPriceProviderError),
    // Leaky abstraction, these errors should not propagate here.
    #[error(transparent)]
    NetworkError(ClientError),
}

Copy link
Contributor Author

@guy-starkware guy-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 4 of 9 files reviewed, 10 unresolved discussions (waiting on @asmaastarkware and @matan-starkware)


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 140 at r3 (raw file):

Previously, guy-starkware wrote…

Yes, I'll move it to the next PR.

I actually do need the error type.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 23 at r5 (raw file):

Previously, matan-starkware wrote…

This should get moved into the infra "types" crate when we make that

Should I do it in the next PR or here?


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 27 at r5 (raw file):

Previously, matan-starkware wrote…

Just to be clear, from the perspective of the scraper these will remain in the config, it has no idea about versioned constants. From the global perspective, yes the source of these values is versioned constants.

got it.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 46 at r5 (raw file):

Previously, matan-starkware wrote…

Why not copy the defaults from Gilad's L1ScraperConfig?

Good question. I'll copy them.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 132 at r5 (raw file):

Previously, matan-starkware wrote…

Move this above the impls (I recommend putting this above the structs which have impls also)

https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/style.md#order-of-items

Done.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper_test.rs line 56 at r3 (raw file):

Previously, matan-starkware wrote…

It is complicated for 2 reasons:

  1. Associated types - can get this to work by following the automock documentation. I made a new error type for the example below
  2. Parameters with lifetimes - this one seems to be a known issue which unfortunately requires changing the function definition, but luckily seems to be just a boilerplate change, nothing too complex to implement. Can't mock trait containing function that takes an argument of type Option<&T> asomers/mockall#61

I lean towards this being worthwhile, but let's check with Gilad what he thinks about making this change.

#[derive(thiserror::Error, Debug)]
pub enum MockError {}

/// Interface for getting data from the Starknet base contract.
#[cfg_attr(test, automock(type Error = MockError;))]
#[async_trait]
pub trait BaseLayerContract {
    type Error: Error + Display + Debug;

    /// Get specific events from the Starknet base contract between two L1 block numbers.
    async fn events<'a>(
        &'a self,
        block_range: RangeInclusive<L1BlockNumber>,
        event_identifiers: &'a [&'a str],
    ) -> Result<Vec<L1Event>, Self::Error>;

}

I'll give it another try. The mock error certainly helps.

@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch from 4c696c9 to ffafcb6 Compare February 12, 2025 14:38
@matan-starkware matan-starkware self-requested a review February 13, 2025 08:23
Copy link
Contributor

@matan-starkware matan-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 1 files at r6, all commit messages.
Reviewable status: 5 of 9 files reviewed, 9 unresolved discussions (waiting on @asmaastarkware and @guy-starkware)


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 23 at r5 (raw file):

Previously, guy-starkware wrote…

Should I do it in the next PR or here?

If we're doing this it's a separate PR. What did infra team say about this?
If they don't want a separate crate I want the files to be separate for this stuff.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper_test.rs line 56 at r3 (raw file):

Previously, guy-starkware wrote…

I'll give it another try. The mock error certainly helps.

Ok I would rather see the tests using the mock, so can you make this change in a separate PR, send it to me and Asmaa and then rebase this PR on top of that one?


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 53 at r6 (raw file):

    fn default() -> Self {
        Self {
            l1_block_to_start_scraping_from: 0,

I'm ok with this in the config so that we can pass an override in from the command line, but I don't want scraper reading this field. The scraper should have next_block_number_to_fetch configured when built.

Please add a comment about this.

Code quote:

            l1_block_to_start_scraping_from: 0,

crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 130 at r6 (raw file):

        }

        Ok(())

Remove this. Just scrape from self.next_height until the provider doesn't know the block (returns None)

Suggestion:

        loop {
            let price_sample = self
                .base_layer
                .get_price_sample(self.next_block_number_to_fetch)
                .await
                .map_err(L1GasPriceScraperError::BaseLayerError)?;
            match price_sample {
                None => break,
                Some(sample) => {
                    self.l1_gas_price_provider
                        .add_price_info(BlockNumber(block_number), sample)
                        .map_err(L1GasPriceScraperError::GasPriceProviderError)?;
                    self.next_block_number_to_fetch += 1;
                }
            }
        }

        Ok(())

Copy link
Contributor Author

@guy-starkware guy-starkware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 5 of 9 files reviewed, 9 unresolved discussions (waiting on @asmaastarkware and @matan-starkware)


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 85 at r3 (raw file):

Previously, matan-starkware wrote…

Finality is an ambiguous term that refers to when we are confident that a block will not be reverted.

In the strict sense it means that over 2/3 of validators would need to fork the system (in most PoS systems). In Tendermint this happens immediately (e.g. as soon as H is decided). For Ethereum this means basically that 2 epochs (64 blocks) have passed. This though takes 12m and we don't want to wait that long due to stale data.

In practice people use much smaller numbers of blocks for finality; an acceptable probability that the block will not reorg. If I remember correctly, Ethereum switch to PoS about 2.5 years ago and has never had a reorg longer than 2 blocks. So we use 3 blocks as our finality, as this is only 30s of lag from the tip of Ethereum and has good enough likelihood to be stable. It is though configurable.

👍


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 23 at r5 (raw file):

Previously, matan-starkware wrote…

If we're doing this it's a separate PR. What did infra team say about this?
If they don't want a separate crate I want the files to be separate for this stuff.

I haven't asked them yet, but from looking at so many examples in other crates I think this is what is expected. I'm adding anothe PR.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 53 at r6 (raw file):

Previously, matan-starkware wrote…

I'm ok with this in the config so that we can pass an override in from the command line, but I don't want scraper reading this field. The scraper should have next_block_number_to_fetch configured when built.

Please add a comment about this.

Done.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper.rs line 130 at r6 (raw file):

Previously, matan-starkware wrote…

Remove this. Just scrape from self.next_height until the provider doesn't know the block (returns None)

Done.


crates/starknet_l1_gas_price/src/l1_gas_price_scraper_test.rs line 56 at r3 (raw file):

Previously, matan-starkware wrote…

Ok I would rather see the tests using the mock, so can you make this change in a separate PR, send it to me and Asmaa and then rebase this PR on top of that one?

I think I can make it work.

@guy-starkware guy-starkware force-pushed the guyn/l1price/l1_gas_price_scraper branch from ffafcb6 to 88519bb Compare February 13, 2025 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants