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

Use gas prices from actual blocks to calculate estimate gas prices #2501

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

MitchTurner
Copy link
Member

@MitchTurner MitchTurner commented Dec 16, 2024

Linked Issues/PRs

Description

Because some nodes won't be running the gas price algorithm, or might not be synced properly, we are going to calculate the price based on the latest block gas price. That is a reliable source of truth.

Checklist

  • Breaking changes are clearly marked as such in the PR description and changelog
  • New behavior is reflected in tests
  • The specification matches the implemented behavior (link update PR if changes are needed)

Before requesting review

  • I have reviewed the code myself
  • I have created follow-up issues caused by this PR and linked them here

After merging, notify other teams

[Add or remove entries as needed]

@MitchTurner MitchTurner marked this pull request as ready for review December 16, 2024 04:33
Copy link
Member

@rymnc rymnc left a comment

Choose a reason for hiding this comment

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

left a few questions :)

}

#[async_trait::async_trait]
impl GasPriceEstimate for ArcGasPriceEstimate<u32, u64> {
Copy link
Member

Choose a reason for hiding this comment

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

does this have to be async?

pub(crate) fn update_latest_gas_price(&self, block_info: &BlockInfo) {
match block_info {
BlockInfo::GenesisBlock => {
// do nothing
Copy link
Member

Choose a reason for hiding this comment

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

should we have a test to see what values are stored in the block after genesis? afaiu we test only post genesis transitions

@@ -481,10 +506,12 @@ mod tests {
),
None,
);
let latest_gas_price = Arc::new(parking_lot::RwLock::new((0, 0)));
Copy link
Member

Choose a reason for hiding this comment

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

curious why we need this when perhaps the algo could contain this internally?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah... That works too. It's kinda a separate value so I thought to keep it separate but that would work.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, maybe we oversplitting here. Algorithm could answer this, if we store the last used gas price there. But I think Mitch wants to reuse the code for v0. But maybe it can live in common and used in both algorithm.

use super::*;
use proptest::proptest;

async fn _worst_case__correctly_calculates_value(
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this function is called later on

Suggested change
async fn _worst_case__correctly_calculates_value(
async fn worst_case__correctly_calculates_value(

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. It's the actual test code, but I don't like writing it inside the prop_test macro because the IDE has a hard time parsing it:

proptest! {
    #[test]
    fn worst_case__correctly_calculates_value(
        price: u64,
        starting_height: u32,
        block_horizon in 0..10_000u32,
        percentage: u64
    ) {
        _worst_case__correctly_calculates_value(price, starting_height, block_horizon, percentage);
    }
}

So this is my naming convention for that case.

}

#[allow(dead_code)]
pub struct ArcGasPriceEstimate<Height, GasPrice> {
Copy link
Contributor

@acerone85 acerone85 Dec 16, 2024

Choose a reason for hiding this comment

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

would it be possible to have a bit of documentation here?
In particular, I'd like to understand what the "height" and "price" refer to here, since later they are named as "best_height" and "best_price"


#[async_trait::async_trait]
impl GasPriceEstimate for ArcGasPriceEstimate<u32, u64> {
async fn worst_case_gas_price(&self, height: BlockHeight) -> Option<u64> {
Copy link
Contributor

@acerone85 acerone85 Dec 16, 2024

Choose a reason for hiding this comment

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

so what I get here is that we assume that the price is going to raise by the specified percentage every block until height is reached, is that correct?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct.

best_gas_price: u64,
best_height: u32,
percentage: u64,
target_height: u32,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you can pass directly the difference of target_height and best_height to this function and get rid of one parameter, but I don't have a strong opinion here

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm fine with this for now.


#[allow(clippy::cast_possible_truncation)]
pub(crate) fn cumulative_percentage_change(
best_gas_price: u64,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe start_gas_price is better?

let multiple = (1.0f64 + percentage_as_decimal).powf(blocks);
let mut approx = best_gas_price as f64 * multiple;
// account for rounding errors and take a slightly higher value
const ROUNDING_ERROR_CUTOFF: f64 = 16948547188989277.0;
Copy link
Contributor

Choose a reason for hiding this comment

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

can you put a comment about how this value has been obtained?

// account for rounding errors and take a slightly higher value
const ROUNDING_ERROR_CUTOFF: f64 = 16948547188989277.0;
if approx > ROUNDING_ERROR_CUTOFF {
const ROUNDING_ERROR_COMPENSATION: f64 = 2000.0;
Copy link
Contributor

Choose a reason for hiding this comment

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

expected = expected.saturating_add(change_amount);
}

assert!(actual >= expected);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can be more precise here, and place an upper bound on actual, e.g.

Suggested change
assert!(actual >= expected);
assert!(actual >= expected);
assert!(actual <= expected.saturating_add(2000.0))

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure. But we don't actually care about the lower bound of this. In reality, the gas price given by the block producer will be much lower than what we estimate 99% of the time. So we only really care that the block producer never gives a value higher than what we estimate.

@acerone85
Copy link
Contributor

I'm not sure I understand what the role of ArcGasPriceEstimate is in this PR, since it seems to be used only in tests

Comment on lines 203 to 204
let best_gas_price = self.get_gas_price();
let best_height = self.get_height();
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can have a race condition here, because between self.get_gas_price and self.get_height the value can change. You can lock it, read both values, and unlock it again to avoid this issue.

@@ -105,6 +126,7 @@ where
pub fn new(
l2_block_source: L2,
shared_algo: SharedV1Algorithm,
latest_gas_price: Arc<parking_lot::RwLock<(u32, u64)>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be nice to have a type that hides this lock(Arc<parking_lot::RwLock<(u32, u64)>>) inside. Later we can easier refactor it, plus you will not expose direct access to the lock.

@@ -481,10 +506,12 @@ mod tests {
),
None,
);
let latest_gas_price = Arc::new(parking_lot::RwLock::new((0, 0)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, maybe we oversplitting here. Algorithm could answer this, if we store the last used gas price there. But I think Mitch wants to reuse the code for v0. But maybe it can live in common and used in both algorithm.

@MitchTurner
Copy link
Member Author

I'm not sure I understand what the role of ArcGasPriceEstimate is in this PR, since it seems to be used only in tests
Yeah. It will be used by the GraphQL service. Just showing that it works here.

xgreenx
xgreenx previously approved these changes Jan 2, 2025
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