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(gas_price_service_v1): include max_da_gas_price to control volatility #2541

Merged
merged 10 commits into from
Jan 9, 2025
7 changes: 7 additions & 0 deletions bin/fuel-core/src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ pub struct Command {
#[arg(long = "min-da-gas-price", default_value = "10000000", env)]
pub min_da_gas_price: u64,

/// Maximum DA gas price
// DEV: ensure that the max_da_gas_price default is > then the min_da_gas_price default
rafal-ch marked this conversation as resolved.
Show resolved Hide resolved
#[arg(long = "max-da-gas-price", default_value = "10000001", env)]
pub max_da_gas_price: u64,

/// P component of DA gas price calculation
/// **NOTE**: This is the **inverse** gain of a typical P controller.
/// Increasing this value will reduce gas price fluctuations.
Expand Down Expand Up @@ -339,6 +344,7 @@ impl Command {
min_gas_price,
gas_price_threshold_percent,
min_da_gas_price,
max_da_gas_price,
da_p_component,
da_d_component,
max_da_gas_price_change_percent,
Expand Down Expand Up @@ -660,6 +666,7 @@ impl Command {
memory_pool_size,
da_gas_price_factor: NonZeroU64::new(100).expect("100 is not zero"),
min_da_gas_price,
max_da_gas_price,
max_da_gas_price_change_percent,
da_p_component,
da_d_component,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl From<Config> for GasPriceServiceConfig {
value.exec_gas_price_threshold_percent,
value.da_gas_price_factor,
value.min_da_gas_price,
value.max_da_gas_price,
value.max_da_gas_price_change_percent,
value.da_p_component,
value.da_d_component,
Expand Down
2 changes: 2 additions & 0 deletions crates/fuel-core/src/service/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub struct Config {
pub memory_pool_size: usize,
pub da_gas_price_factor: NonZeroU64,
pub min_da_gas_price: u64,
pub max_da_gas_price: u64,
pub max_da_gas_price_change_percent: u16,
pub da_p_component: i64,
pub da_d_component: i64,
Expand Down Expand Up @@ -216,6 +217,7 @@ impl Config {
memory_pool_size: 4,
da_gas_price_factor: NonZeroU64::new(100).expect("100 is not zero"),
min_da_gas_price: 0,
max_da_gas_price: 1,
max_da_gas_price_change_percent: 0,
da_p_component: 0,
da_d_component: 0,
Expand Down
22 changes: 18 additions & 4 deletions crates/fuel-gas-price-algorithm/src/v1.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::utils::cumulative_percentage_change;
use std::{
cmp::max,
cmp::{
max,
min,
},
collections::BTreeMap,
num::NonZeroU64,
ops::{
Expand Down Expand Up @@ -147,6 +150,8 @@ pub struct AlgorithmUpdaterV1 {
pub gas_price_factor: NonZeroU64,
/// The lowest the algorithm allows the da gas price to go
pub min_da_gas_price: u64,
/// The highest the algorithm allows the da gas price to go
pub max_da_gas_price: u64,
/// The maximum percentage that the DA portion of the gas price can change in a single block
/// Using `u16` because it can go above 100% and possibly over 255%
pub max_da_gas_price_change_percent: u16,
Expand Down Expand Up @@ -514,9 +519,12 @@ impl AlgorithmUpdaterV1 {
scaled_da_change,
maybe_new_scaled_da_gas_price
);
self.new_scaled_da_gas_price = max(
self.min_scaled_da_gas_price(),
maybe_new_scaled_da_gas_price,
self.new_scaled_da_gas_price = min(
max(
self.min_scaled_da_gas_price(),
maybe_new_scaled_da_gas_price,
),
self.max_scaled_da_gas_price(),
Comment on lines +523 to +527
Copy link
Member

Choose a reason for hiding this comment

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

Let's add some tests for this.

Copy link
Member Author

Choose a reason for hiding this comment

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

addressed in 4c42380

);
}

Expand All @@ -540,6 +548,12 @@ impl AlgorithmUpdaterV1 {
.saturating_mul(self.gas_price_factor.into())
}

fn max_scaled_da_gas_price(&self) -> u64 {
// note: here we make sure that a correct maximum is used; hacky :(
max(self.max_da_gas_price, self.min_da_gas_price)
.saturating_mul(self.gas_price_factor.into())
}

fn p(&self) -> i128 {
let upcast_p = i128::from(self.da_p_component);
let checked_p = self.last_profit.checked_div(upcast_p);
Expand Down
8 changes: 8 additions & 0 deletions crates/fuel-gas-price-algorithm/src/v1/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct BlockBytes {
pub struct UpdaterBuilder {
min_exec_gas_price: u64,
min_da_gas_price: u64,
max_da_gas_price: u64,
starting_exec_gas_price: u64,
starting_da_gas_price: u64,
exec_gas_price_change_percent: u16,
Expand Down Expand Up @@ -53,6 +54,7 @@ impl UpdaterBuilder {
Self {
min_exec_gas_price: 0,
min_da_gas_price: 0,
max_da_gas_price: 1,
starting_exec_gas_price: 1,
starting_da_gas_price: 1,
exec_gas_price_change_percent: 0,
Expand Down Expand Up @@ -86,6 +88,11 @@ impl UpdaterBuilder {
self
}

fn with_max_da_gas_price(mut self, max_price: u64) -> Self {
self.max_da_gas_price = max_price;
self
}

fn with_starting_exec_gas_price(mut self, starting_da_gas_price: u64) -> Self {
self.starting_exec_gas_price = starting_da_gas_price;
self
Expand Down Expand Up @@ -192,6 +199,7 @@ impl UpdaterBuilder {
last_profit: self.last_profit,
second_to_last_profit: self.second_to_last_profit,
min_da_gas_price: self.min_da_gas_price,
max_da_gas_price: self.max_da_gas_price,
gas_price_factor: self
.da_gas_price_factor
.try_into()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ fn update_da_record_data__da_block_lowers_da_gas_price() {
fn update_da_record_data__da_block_increases_da_gas_price() {
// given
let da_cost_per_byte = 40;
let max_da_gas_price = u64::MAX;
let l2_block_height = 11;
let original_known_total_cost = 150;
let mut unrecorded_blocks: BTreeMap<_, _> = [(11, 3000)].into_iter().collect();
Expand All @@ -320,6 +321,7 @@ fn update_da_record_data__da_block_increases_da_gas_price() {
.with_projected_total_cost(projected_total_cost as u128)
.with_known_total_cost(original_known_total_cost as u128)
.with_unrecorded_blocks(&unrecorded_blocks)
.with_max_da_gas_price(max_da_gas_price)
.build();

let new_cost_per_byte = 100;
Expand Down Expand Up @@ -355,6 +357,126 @@ fn update_da_record_data__da_block_increases_da_gas_price() {
assert_ne!(old_da_gas_price, new_da_gas_price);
}

#[test]
fn update_da_record_data__da_block_increases_da_gas_price_within_the_min_max_range() {
// given
let min_da_gas_price = 0;
let max_da_gas_price = 5;
let da_cost_per_byte = 40;
let l2_block_height = 11;
let original_known_total_cost = 150;
let mut unrecorded_blocks: BTreeMap<_, _> = [(11, 3000)].into_iter().collect();
let da_p_component = 2;
let guessed_cost: u64 = unrecorded_blocks
.values()
.map(|bytes| bytes * da_cost_per_byte)
.sum();
let projected_total_cost = original_known_total_cost + guessed_cost;

let mut updater = UpdaterBuilder::new()
.with_da_cost_per_byte(da_cost_per_byte as u128)
.with_da_p_component(da_p_component)
.with_last_profit(-10, 0)
.with_l2_block_height(l2_block_height)
.with_projected_total_cost(projected_total_cost as u128)
.with_known_total_cost(original_known_total_cost as u128)
.with_unrecorded_blocks(&unrecorded_blocks)
.with_min_da_gas_price(min_da_gas_price)
.with_max_da_gas_price(max_da_gas_price)
.build();

let new_cost_per_byte = 100;
let (recorded_heights, recorded_cost) = unrecorded_blocks.iter().fold(
(vec![], 0),
|(mut range, cost), (height, bytes)| {
range.push(height);
(range, cost + bytes * new_cost_per_byte)
},
);

let min = *recorded_heights.iter().min().unwrap();
let max = *recorded_heights.iter().max().unwrap();
let recorded_range = *min..=*max;
let recorded_bytes = 500;

let old_da_gas_price = updater.new_scaled_da_gas_price;

// when
updater
.update_da_record_data(
recorded_range,
recorded_bytes,
recorded_cost as u128,
&mut unrecorded_blocks,
)
.unwrap();

// then
let new_da_gas_price = updater.new_scaled_da_gas_price;
// because the profit is -10 and the da_p_component is 2, the new da gas price should be greater than the previous one.
assert_eq!(new_da_gas_price, max_da_gas_price);
assert_ne!(old_da_gas_price, new_da_gas_price);
}

#[test]
fn update_da_record_data__sets_da_gas_price_to_min_da_gas_price_when_max_lt_min() {
// given
let min_da_gas_price = 1;
let max_da_gas_price = 0;
let da_cost_per_byte = 40;
let l2_block_height = 11;
let original_known_total_cost = 150;
let mut unrecorded_blocks: BTreeMap<_, _> = [(11, 3000)].into_iter().collect();
let da_p_component = 2;
let guessed_cost: u64 = unrecorded_blocks
.values()
.map(|bytes| bytes * da_cost_per_byte)
.sum();
let projected_total_cost = original_known_total_cost + guessed_cost;

let mut updater = UpdaterBuilder::new()
.with_da_cost_per_byte(da_cost_per_byte as u128)
.with_da_p_component(da_p_component)
.with_last_profit(-10, 0)
.with_l2_block_height(l2_block_height)
.with_projected_total_cost(projected_total_cost as u128)
.with_known_total_cost(original_known_total_cost as u128)
.with_unrecorded_blocks(&unrecorded_blocks)
.with_min_da_gas_price(min_da_gas_price)
.with_max_da_gas_price(max_da_gas_price)
.build();

let new_cost_per_byte = 100;
let (recorded_heights, recorded_cost) = unrecorded_blocks.iter().fold(
(vec![], 0),
|(mut range, cost), (height, bytes)| {
range.push(height);
(range, cost + bytes * new_cost_per_byte)
},
);

let min = *recorded_heights.iter().min().unwrap();
let max = *recorded_heights.iter().max().unwrap();
let recorded_range = *min..=*max;
let recorded_bytes = 500;

// when
updater
.update_da_record_data(
recorded_range,
recorded_bytes,
recorded_cost as u128,
&mut unrecorded_blocks,
)
.unwrap();

// then
let new_da_gas_price = updater.new_scaled_da_gas_price;

// because max_da_gas_price = 0 and < min_da_gas_price = 1, the new da gas price should be min_da_gas_price
assert_eq!(new_da_gas_price, min_da_gas_price);
}

#[test]
fn update_da_record_data__da_block_will_not_change_da_gas_price() {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ fn negative_profit_updater_builder() -> UpdaterBuilder {
let starting_da_gas_price = 100;
let starting_cost = u128::MAX;
let latest_gas_per_byte = i32::MAX; // DA is very expensive
let max_da_gas_price = u64::MAX;
let da_p_component = 100;
let da_d_component = 10;
let last_profit = i128::MIN;
Expand All @@ -39,6 +40,7 @@ fn negative_profit_updater_builder() -> UpdaterBuilder {
.with_projected_total_cost(starting_cost)
.with_da_cost_per_byte(latest_gas_per_byte as u128)
.with_last_profit(last_profit, last_last_profit)
.with_max_da_gas_price(max_da_gas_price)
}

fn positive_profit_updater_builder() -> UpdaterBuilder {
Expand Down Expand Up @@ -399,6 +401,7 @@ fn update_l2_block_data__price_does_not_decrease_more_than_max_percent() {
let last_profit = i128::MAX; // Large, positive profit to decrease da price
let last_last_profit = 0;
let max_da_change_percent = 5;
let max_da_gas_price = u64::MAX;
let large_starting_reward = i128::MAX;
let mut updater = UpdaterBuilder::new()
.with_starting_exec_gas_price(starting_exec_gas_price)
Expand All @@ -411,6 +414,7 @@ fn update_l2_block_data__price_does_not_decrease_more_than_max_percent() {
.with_da_cost_per_byte(latest_gas_per_byte as u128)
.with_last_profit(last_profit, last_last_profit)
.with_da_max_change_percent(max_da_change_percent)
.with_max_da_gas_price(max_da_gas_price)
.build();
let unrecorded_blocks = &mut empty_unrecorded_blocks();

Expand Down Expand Up @@ -446,6 +450,7 @@ fn update_l2_block_data__da_price_does_not_increase_more_than_max_percent() {
let last_profit = i128::MIN; // Large, negative profit to increase da price
let last_last_profit = 0;
let max_da_change_percent = 5;
let max_da_gas_price = u64::MAX;
let large_starting_reward = 0;
let unrecorded_blocks = &mut empty_unrecorded_blocks();
let mut updater = UpdaterBuilder::new()
Expand All @@ -459,6 +464,7 @@ fn update_l2_block_data__da_price_does_not_increase_more_than_max_percent() {
.with_da_cost_per_byte(latest_gas_per_byte)
.with_last_profit(last_profit, last_last_profit)
.with_da_max_change_percent(max_da_change_percent)
.with_max_da_gas_price(max_da_gas_price)
.build();

// when
Expand Down Expand Up @@ -487,6 +493,7 @@ fn update_l2_block_data__never_drops_below_minimum_da_gas_price() {
let starting_exec_gas_price = 0;
let last_da_gas_price = 100;
let min_da_gas_price = 100;
let max_da_gas_price = min_da_gas_price + 1;
let starting_cost = 0;
let latest_gas_per_byte = 0; // DA is free
let da_p_component = 100;
Expand All @@ -507,6 +514,7 @@ fn update_l2_block_data__never_drops_below_minimum_da_gas_price() {
.with_da_cost_per_byte(latest_gas_per_byte as u128)
.with_last_profit(last_profit, avg_window)
.with_min_da_gas_price(min_da_gas_price)
.with_max_da_gas_price(max_da_gas_price)
.build();

// when
Expand Down Expand Up @@ -534,6 +542,7 @@ fn update_l2_block_data__even_profit_maintains_price() {
// given
let starting_exec_gas_price = 100;
let starting_da_gas_price = 100;
let max_da_gas_price = u64::MAX;
let starting_cost = 500;
let latest_cost_per_byte = 10;
let da_gas_price_denominator = 1;
Expand All @@ -548,6 +557,7 @@ fn update_l2_block_data__even_profit_maintains_price() {
.with_known_total_cost(starting_cost as u128)
.with_projected_total_cost(starting_cost as u128)
.with_da_cost_per_byte(latest_cost_per_byte as u128)
.with_max_da_gas_price(max_da_gas_price)
.build();

// when
Expand Down
2 changes: 2 additions & 0 deletions crates/services/gas_price_service/src/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ impl GasPriceServiceConfig {
l2_block_fullness_threshold_percent: u8,
gas_price_factor: NonZeroU64,
min_da_gas_price: u64,
max_da_gas_price: u64,
max_da_gas_price_change_percent: u16,
da_p_component: i64,
da_d_component: i64,
Expand All @@ -114,6 +115,7 @@ impl GasPriceServiceConfig {
l2_block_fullness_threshold_percent,
gas_price_factor,
min_da_gas_price,
max_da_gas_price,
max_da_gas_price_change_percent,
da_p_component,
da_d_component,
Expand Down
3 changes: 3 additions & 0 deletions crates/services/gas_price_service/src/v1/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub struct V1AlgorithmConfig {
// https://github.com/FuelLabs/fuel-core/issues/2481
pub gas_price_factor: NonZeroU64,
pub min_da_gas_price: u64,
pub max_da_gas_price: u64,
pub max_da_gas_price_change_percent: u16,
pub da_p_component: i64,
pub da_d_component: i64,
Expand Down Expand Up @@ -108,6 +109,7 @@ pub fn updater_from_config(value: &V1AlgorithmConfig) -> AlgorithmUpdaterV1 {
.l2_block_fullness_threshold_percent
.into(),
min_da_gas_price: value.min_da_gas_price,
max_da_gas_price: value.max_da_gas_price,
max_da_gas_price_change_percent: value.max_da_gas_price_change_percent,
da_p_component: value.da_p_component,
da_d_component: value.da_d_component,
Expand Down Expand Up @@ -166,6 +168,7 @@ pub fn v1_algorithm_from_metadata(
.l2_block_fullness_threshold_percent
.into(),
min_da_gas_price: config.min_da_gas_price,
max_da_gas_price: config.max_da_gas_price,
max_da_gas_price_change_percent: config.max_da_gas_price_change_percent,
da_p_component: config.da_p_component,
da_d_component: config.da_d_component,
Expand Down
Loading
Loading