-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
base: master
Are you sure you want to change the base?
Changes from 8 commits
181430e
f3eba7b
54447de
00d7c9a
1ad2f60
f5f80e3
9a35d5d
b5eccc1
dd9ad08
9e7af07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Seeds for failure cases proptest has generated in the past. It is | ||
# automatically read and these particular cases re-run before any | ||
# novel cases are generated. | ||
# | ||
# It is recommended to check this file in to source control so that | ||
# everyone who runs the test benefits from these saved cases. | ||
cc 87eb4d9c7c90c2a0ee11edef3ee1b01303db0a6ba1fd34d7fc6146a2c36387f4 # shrinks to gas_price = 1, starting_height = 0, block_horizon = 1, percentage = 100 |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,8 +1,23 @@ | ||||||
use crate::{ | ||||||
database::{ | ||||||
database_description::relayer::Relayer, | ||||||
Database, | ||||||
}, | ||||||
fuel_core_graphql_api::ports::GasPriceEstimate, | ||||||
service::{ | ||||||
sub_services::{ | ||||||
BlockProducerService, | ||||||
TxPoolSharedState, | ||||||
}, | ||||||
vm_pool::MemoryPool, | ||||||
}, | ||||||
}; | ||||||
use fuel_core_consensus_module::{ | ||||||
block_verifier::Verifier, | ||||||
RelayerConsensusConfig, | ||||||
}; | ||||||
use fuel_core_executor::executor::OnceTransactionsSource; | ||||||
use fuel_core_gas_price_service::v1::service::LatestGasPrice; | ||||||
use fuel_core_importer::ImporterResult; | ||||||
use fuel_core_poa::{ | ||||||
ports::BlockSigner, | ||||||
|
@@ -19,6 +34,7 @@ use fuel_core_types::{ | |||||
consensus::Consensus, | ||||||
}, | ||||||
fuel_tx::Transaction, | ||||||
fuel_types::BlockHeight, | ||||||
services::{ | ||||||
block_importer::SharedImportResult, | ||||||
block_producer::Components, | ||||||
|
@@ -32,20 +48,6 @@ use fuel_core_types::{ | |||||
use fuel_core_upgradable_executor::executor::Executor; | ||||||
use std::sync::Arc; | ||||||
|
||||||
use crate::{ | ||||||
database::{ | ||||||
database_description::relayer::Relayer, | ||||||
Database, | ||||||
}, | ||||||
service::{ | ||||||
sub_services::{ | ||||||
BlockProducerService, | ||||||
TxPoolSharedState, | ||||||
}, | ||||||
vm_pool::MemoryPool, | ||||||
}, | ||||||
}; | ||||||
|
||||||
pub mod block_importer; | ||||||
pub mod consensus_module; | ||||||
pub mod consensus_parameters_provider; | ||||||
|
@@ -85,6 +87,162 @@ impl StaticGasPrice { | |||||
} | ||||||
} | ||||||
|
||||||
#[cfg(test)] | ||||||
mod arc_gas_price_estimate_tests { | ||||||
#![allow(non_snake_case)] | ||||||
|
||||||
use super::*; | ||||||
use proptest::proptest; | ||||||
|
||||||
async fn _worst_case__correctly_calculates_value( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this function is called later on
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
So this is my naming convention for that case. |
||||||
gas_price: u64, | ||||||
starting_height: u32, | ||||||
block_horizon: u32, | ||||||
percentage: u16, | ||||||
) { | ||||||
// given | ||||||
let subject = ArcGasPriceEstimate::new(starting_height, gas_price, percentage); | ||||||
|
||||||
// when | ||||||
let target_height = starting_height.saturating_add(block_horizon); | ||||||
let estimated = subject | ||||||
.worst_case_gas_price(target_height.into()) | ||||||
.await | ||||||
.unwrap(); | ||||||
|
||||||
// then | ||||||
let mut actual = gas_price; | ||||||
|
||||||
for _ in 0..block_horizon { | ||||||
let change_amount = | ||||||
actual.saturating_mul(percentage as u64).saturating_div(100); | ||||||
actual = actual.saturating_add(change_amount); | ||||||
} | ||||||
|
||||||
assert!(estimated >= actual); | ||||||
} | ||||||
|
||||||
proptest! { | ||||||
#[test] | ||||||
fn worst_case_gas_price__correctly_calculates_value( | ||||||
gas_price: u64, | ||||||
starting_height: u32, | ||||||
block_horizon in 0..10_000u32, | ||||||
percentage: u16, | ||||||
) { | ||||||
let rt = tokio::runtime::Runtime::new().unwrap(); | ||||||
rt.block_on(_worst_case__correctly_calculates_value( | ||||||
gas_price, | ||||||
starting_height, | ||||||
block_horizon, | ||||||
percentage, | ||||||
)); | ||||||
} | ||||||
} | ||||||
|
||||||
proptest! { | ||||||
#[test] | ||||||
fn worst_case_gas_price__never_overflows( | ||||||
gas_price: u64, | ||||||
starting_height: u32, | ||||||
block_horizon in 0..10_000u32, | ||||||
percentage: u16 | ||||||
) { | ||||||
let rt = tokio::runtime::Runtime::new().unwrap(); | ||||||
|
||||||
// given | ||||||
let subject = ArcGasPriceEstimate::new(starting_height, gas_price, percentage); | ||||||
|
||||||
// when | ||||||
let target_height = starting_height.saturating_add(block_horizon); | ||||||
|
||||||
let _ = rt.block_on(subject.worst_case_gas_price(target_height.into())); | ||||||
|
||||||
// then | ||||||
// doesn't panic with an overflow | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
/// Allows communication from other service with more recent gas price data | ||||||
/// `Height` refers to the height of the block at which the gas price was last updated | ||||||
/// `GasPrice` refers to the gas price at the last updated block | ||||||
#[allow(dead_code)] | ||||||
pub struct ArcGasPriceEstimate<Height, GasPrice> { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it be possible to have a bit of documentation here? |
||||||
/// Shared state of latest gas price data | ||||||
latest_gas_price: LatestGasPrice<Height, GasPrice>, | ||||||
/// The max percentage the gas price can increase per block | ||||||
percentage: u16, | ||||||
} | ||||||
|
||||||
impl<Height, GasPrice> ArcGasPriceEstimate<Height, GasPrice> { | ||||||
#[cfg(test)] | ||||||
pub fn new(height: Height, price: GasPrice, percentage: u16) -> Self { | ||||||
let latest_gas_price = LatestGasPrice::new(height, price); | ||||||
Self { | ||||||
latest_gas_price, | ||||||
percentage, | ||||||
} | ||||||
} | ||||||
|
||||||
pub fn new_from_inner( | ||||||
inner: LatestGasPrice<Height, GasPrice>, | ||||||
percentage: u16, | ||||||
) -> Self { | ||||||
Self { | ||||||
latest_gas_price: inner, | ||||||
percentage, | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
impl<Height: Copy, GasPrice: Copy> ArcGasPriceEstimate<Height, GasPrice> { | ||||||
fn get_height_and_gas_price(&self) -> (Height, GasPrice) { | ||||||
self.latest_gas_price.get() | ||||||
} | ||||||
} | ||||||
|
||||||
#[async_trait::async_trait] | ||||||
impl GasPriceEstimate for ArcGasPriceEstimate<u32, u64> { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this have to be async? |
||||||
async fn worst_case_gas_price(&self, height: BlockHeight) -> Option<u64> { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. |
||||||
let (best_height, best_gas_price) = self.get_height_and_gas_price(); | ||||||
let percentage = self.percentage; | ||||||
|
||||||
let worst = cumulative_percentage_change( | ||||||
best_gas_price, | ||||||
best_height, | ||||||
percentage as u64, | ||||||
height.into(), | ||||||
); | ||||||
Some(worst) | ||||||
} | ||||||
} | ||||||
|
||||||
#[allow(clippy::cast_possible_truncation)] | ||||||
pub(crate) fn cumulative_percentage_change( | ||||||
start_gas_price: u64, | ||||||
best_height: u32, | ||||||
percentage: u64, | ||||||
target_height: u32, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: you can pass directly the difference of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine with this for now. |
||||||
) -> u64 { | ||||||
let blocks = target_height.saturating_sub(best_height) as f64; | ||||||
let percentage_as_decimal = percentage as f64 / 100.0; | ||||||
let multiple = (1.0f64 + percentage_as_decimal).powf(blocks); | ||||||
let mut approx = start_gas_price as f64 * multiple; | ||||||
// Account for rounding errors and take a slightly higher value | ||||||
// Around the `ROUNDING_ERROR_CUTOFF` the rounding errors will cause the estimate to be too low. | ||||||
// We increase by `ROUNDING_ERROR_COMPENSATION` to account for this. | ||||||
// This is an unlikely situation in practice, but we want to guarantee that the actual | ||||||
// gas price is always equal or less than the estimate given here | ||||||
const ROUNDING_ERROR_CUTOFF: f64 = 16948547188989277.0; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you put a comment about how this value has been obtained? |
||||||
if approx > ROUNDING_ERROR_CUTOFF { | ||||||
const ROUNDING_ERROR_COMPENSATION: f64 = 2000.0; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
approx += ROUNDING_ERROR_COMPENSATION; | ||||||
} | ||||||
// `f64` over `u64::MAX` are cast to `u64::MAX` | ||||||
approx.ceil() as u64 | ||||||
} | ||||||
|
||||||
#[derive(Clone)] | ||||||
pub struct PoAAdapter { | ||||||
shared_state: Option<fuel_core_poa::service::SharedState>, | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
merge issue here