Skip to content

Commit

Permalink
Use gas prices from actual blocks to calculate estimate gas prices (#…
Browse files Browse the repository at this point in the history
…2501)

## Linked Issues/PRs
<!-- List of related 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](https://github.com/FuelLabs/fuel-specs/)
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]

- [ ] [Rust SDK](https://github.com/FuelLabs/fuels-rs/)
- [ ] [Sway compiler](https://github.com/FuelLabs/sway/)
- [ ] [Platform
documentation](https://github.com/FuelLabs/devrel-requests/issues/new?assignees=&labels=new+request&projects=&template=NEW-REQUEST.yml&title=%5BRequest%5D%3A+)
(for out-of-organization contributors, the person merging the PR will do
this)
- [ ] Someone else?

---------

Co-authored-by: Aaryamann Challani <[email protected]>
Co-authored-by: Green Baneling <[email protected]>
  • Loading branch information
3 people authored Jan 6, 2025
1 parent a25ec26 commit 8bd4103
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- [2511](https://github.com/FuelLabs/fuel-core/pull/2511): Fix backward compatibility of V0Metadata in gas price db.

### Changed
- [2501](https://github.com/FuelLabs/fuel-core/pull/2501): Use gas price from block for estimating future gas prices
- [2468](https://github.com/FuelLabs/fuel-core/pull/2468): Abstract unrecorded blocks concept for V1 algorithm, create new storage impl. Introduce `TransactionableStorage` trait to allow atomic changes to the storage.
- [2295](https://github.com/FuelLabs/fuel-core/pull/2295): `CombinedDb::from_config` now respects `state_rewind_policy` with tmp RocksDB.
- [2378](https://github.com/FuelLabs/fuel-core/pull/2378): Use cached hash of the topic instead of calculating it on each publishing gossip message.
Expand Down
7 changes: 7 additions & 0 deletions crates/fuel-core/proptest-regressions/service/adapters.txt
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
186 changes: 172 additions & 14 deletions crates/fuel-core/src/service/adapters.rs
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,
Expand All @@ -19,6 +34,7 @@ use fuel_core_types::{
consensus::Consensus,
},
fuel_tx::Transaction,
fuel_types::BlockHeight,
services::{
block_importer::SharedImportResult,
block_producer::Components,
Expand All @@ -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;
Expand Down Expand Up @@ -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(
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> {
/// 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> {
async fn worst_case_gas_price(&self, height: BlockHeight) -> Option<u64> {
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,
) -> 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;
if approx > ROUNDING_ERROR_CUTOFF {
const ROUNDING_ERROR_COMPENSATION: f64 = 2000.0;
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>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ pub fn get_block_info(
block_gas_capacity: block_gas_limit,
block_bytes: Postcard::encode(block).len() as u64,
block_fees: fee,
gas_price,
};
Ok(info)
}
Expand Down
2 changes: 2 additions & 0 deletions crates/services/gas_price_service/src/common/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,7 @@ pub enum BlockInfo {
block_bytes: u64,
// The fees the block has collected
block_fees: u64,
// The gas price used in the block
gas_price: u64,
},
}
1 change: 1 addition & 0 deletions crates/services/gas_price_service/src/v0/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ mod tests {
block_gas_capacity: 100,
block_bytes: 100,
block_fees: 100,
gas_price: 100,
};

let (l2_block_sender, l2_block_receiver) = mpsc::channel(1);
Expand Down
2 changes: 2 additions & 0 deletions crates/services/gas_price_service/src/v0/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ async fn next_gas_price__affected_by_new_l2_block() {
block_gas_capacity: 100,
block_bytes: 100,
block_fees: 100,
gas_price: 100,
};
let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1);
let l2_block_source = FakeL2BlockSource {
Expand Down Expand Up @@ -200,6 +201,7 @@ async fn next__new_l2_block_saves_old_metadata() {
block_gas_capacity: 100,
block_bytes: 100,
block_fees: 100,
gas_price: 100,
};
let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1);
let l2_block_source = FakeL2BlockSource {
Expand Down
Loading

0 comments on commit 8bd4103

Please sign in to comment.