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

Deal with negative feed back loop in DA gas price #2364

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a733d9d
wip
MitchTurner Oct 15, 2024
e4098e8
Add activity concept with safety mode
MitchTurner Oct 16, 2024
ae5011a
Add passing tests for hold cases
MitchTurner Oct 17, 2024
27e8e2a
Add code for decreasing scenario
MitchTurner Oct 17, 2024
0ae623e
Refactor and add more tests
MitchTurner Oct 17, 2024
92eff33
Add test for decrease in decreas activity
MitchTurner Oct 17, 2024
50effd9
Add test for increasing activity
MitchTurner Oct 17, 2024
6249d48
Add test for decreasing activity
MitchTurner Oct 17, 2024
931b36e
Appease Clippy-sama
MitchTurner Oct 17, 2024
0a547c3
Add more activity tests
MitchTurner Oct 17, 2024
afc7691
Update CHANGELOG
MitchTurner Oct 17, 2024
dc40e13
Reorganize
MitchTurner Oct 17, 2024
06780f0
Fix analyzer
MitchTurner Oct 17, 2024
3df911a
Precompute thresholds, make other adjustments to comments and naming
MitchTurner Oct 19, 2024
1674f13
Add comment about expected behavior of helper function
MitchTurner Oct 19, 2024
863678d
Fix spelling
MitchTurner Oct 19, 2024
fdbe6da
Appease Clippy-sama
MitchTurner Oct 19, 2024
c88b557
Clarify doc comment
MitchTurner Oct 21, 2024
12ff4fd
Update crates/fuel-gas-price-algorithm/src/v1.rs
MitchTurner Oct 21, 2024
0bb7c3f
Rename hold and decrease to capped and always decrease
MitchTurner Oct 22, 2024
f8d3aca
Add TODO for gas price types
MitchTurner Oct 22, 2024
95e7c6a
Merge branch 'master' into feature/do-not-increase-da-gas-price-if-no…
MitchTurner Oct 22, 2024
5918754
Merge branch 'master' into feature/do-not-increase-da-gas-price-if-no…
MitchTurner Oct 22, 2024
66cf372
Merge branch 'master' into feature/do-not-increase-da-gas-price-if-no…
MitchTurner Oct 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added
- [2321](https://github.com/FuelLabs/fuel-core/pull/2321): New metrics for the txpool: "The size of transactions in the txpool" (`txpool_tx_size`), "The time spent by a transaction in the txpool in seconds" (`txpool_tx_time_in_txpool_seconds`), The number of transactions in the txpool (`txpool_number_of_transactions`), "The number of transactions pending verification before entering the txpool" (`txpool_number_of_transactions_pending_verification`), "The number of executable transactions in the txpool" (`txpool_number_of_executable_transactions`), "The time it took to select transactions for inclusion in a block in nanoseconds" (`txpool_select_transaction_time_nanoseconds`), The time it took to insert a transaction in the txpool in milliseconds (`txpool_insert_transaction_time_milliseconds`).
- [2347](https://github.com/FuelLabs/fuel-core/pull/2364): Add activity concept in order to protect against infinitely increasing DA gas price scenarios

## [Version 0.40.0]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::*;
use fuel_gas_price_algorithm::v1::AlgorithmUpdaterV1;
use fuel_gas_price_algorithm::v1::{
AlgorithmUpdaterV1,
L2ActivityTracker,
};
use std::{
collections::BTreeMap,
num::NonZeroU64,
Expand Down Expand Up @@ -83,6 +86,7 @@ impl Simulator {
) -> AlgorithmUpdaterV1 {
// Scales the gas price internally, value is arbitrary
let gas_price_factor = 100;
let always_normal_activity = L2ActivityTracker::new_always_normal();
let updater = AlgorithmUpdaterV1 {
min_exec_gas_price: 10,
min_da_gas_price: 10,
Expand All @@ -109,6 +113,7 @@ impl Simulator {
da_d_component,
last_profit: 0,
second_to_last_profit: 0,
l2_activity: always_normal_activity,
};
updater
}
Expand Down
167 changes: 154 additions & 13 deletions crates/fuel-gas-price-algorithm/src/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,8 @@ pub struct AlgorithmUpdaterV1 {
/// This is a percentage of the total capacity of the L2 block
pub l2_block_fullness_threshold_percent: ClampedPercentage,
// DA
/// The gas price for the DA portion of the last block. This can be used to calculate
/// the DA portion of the next block
// pub last_da_gas_price: u64,

/// The gas price (scaled by the `gas_price_factor`) to cover the DA commitment of the next block
pub new_scaled_da_gas_price: u64,

/// Scale factor for the gas price.
pub gas_price_factor: NonZeroU64,
/// The lowest the algorithm allows the da gas price to go
Expand All @@ -151,13 +146,133 @@ pub struct AlgorithmUpdaterV1 {
pub second_to_last_profit: i128,
/// The latest known cost per byte for recording blocks on the DA chain
pub latest_da_cost_per_byte: u128,

/// Activity of L2
pub l2_activity: L2ActivityTracker,
/// The unrecorded blocks that are used to calculate the projected cost of recording blocks
pub unrecorded_blocks: BTreeMap<Height, Bytes>,
}

/// A value that represents a value between 0 and 100. Higher values are clamped to 100
/// The `L2ActivityTracker` tracks the chain activity to determine a safety mode for setting the DA price.
///
/// Because the DA gas price can increase even when no-one is using the network, there is a potential
/// for a negative feedback loop to occur where the gas price increases, further decreasing activity
/// and increasing the gas price. The `L2ActivityTracker` is used to moderate changes to the DA
/// gas price based on the activity of the L2 chain.
///
/// The chain activity is a cumulative measure, updated whenever a new block is processed.
/// For each L2 block, the block usage is a percentage of the block capacity used. If the
/// block usage is below a certain threshold, the chain activity is decreased, if above the threshold,
/// the activity is increased The chain activity exists on a scale
/// between 0 and the sum of the normal, hold, and decrease buffers.
///
/// e.g. if the decrease buffer size is 20, the hold buffer size is 60, and the increase buffer size is 40:
///
/// 0<-- decrease buffer -->20<-- hold buffer -->80<-- normal buffer -->120
MitchTurner marked this conversation as resolved.
Show resolved Hide resolved
///
/// The current chain activity determines the behavior of the DA gas price.
///
/// For healthy behavior, the activity should be in the `normal` range.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
pub struct L2ActivityTracker {
/// The maximum value the chain activity can hit
max_activity: u16,
/// The threshold if the block activity is below, the DA gas price will be held when it would otherwise be increased
hold_activity_threshold: u16,
/// If the chain activity falls below this value, the DA gas price will be decreased when it would otherwise be increased
decrease_activity_threshold: u16,
/// The current activity of the L2 chain
chain_activity: u16,
/// The threshold of block activity below which the chain activity will be decreased
block_activity_threshold: ClampedPercentage,
AurelienFT marked this conversation as resolved.
Show resolved Hide resolved
}

/// Designates the intended behavior of the DA gas price based on the activity of the L2 chain
pub enum DAGasPriceSafetyMode {
/// Should increase DA gas price freely
Normal,
acerone85 marked this conversation as resolved.
Show resolved Hide resolved
/// Should not increase the DA gas price
Hold,
/// Should decrease the DA gas price always
Decrease,
}

impl L2ActivityTracker {
pub fn new_full(
normal_range_size: u16,
hold_range_size: u16,
decrease_range_size: u16,
block_activity_threshold: ClampedPercentage,
) -> Self {
let decrease_activity_threshold = decrease_range_size;
let hold_activity_threshold = decrease_range_size.saturating_add(hold_range_size);
let max_activity = hold_activity_threshold.saturating_add(normal_range_size);
let chain_activity = max_activity;
Self {
max_activity,
hold_activity_threshold,
decrease_activity_threshold,
chain_activity,
block_activity_threshold,
}
}

pub fn new(
normal_range_size: u16,
hold_range_size: u16,
decrease_range_size: u16,
activity: u16,
block_activity_threshold: ClampedPercentage,
) -> Self {
let mut tracker = Self::new_full(
normal_range_size,
hold_range_size,
decrease_range_size,
block_activity_threshold,
);
tracker.chain_activity = activity.min(tracker.max_activity);
tracker
}

pub fn new_always_normal() -> Self {
let normal_range_size = 100;
let hold_range_size = 0;
let decrease_range_size = 0;
let percentage = ClampedPercentage::new(0);
Self::new(
normal_range_size,
hold_range_size,
decrease_range_size,
100,
percentage,
)
}

pub fn safety_mode(&self) -> DAGasPriceSafetyMode {
if self.chain_activity > self.hold_activity_threshold {
DAGasPriceSafetyMode::Normal
} else if self.chain_activity > self.decrease_activity_threshold {
DAGasPriceSafetyMode::Hold
} else {
DAGasPriceSafetyMode::Decrease
}
}

pub fn update(&mut self, block_usage: ClampedPercentage) {
if block_usage < self.block_activity_threshold {
self.chain_activity = self.chain_activity.saturating_sub(1);
} else {
self.chain_activity =
self.chain_activity.saturating_add(1).min(self.max_activity);
}
}

pub fn current_activity(&self) -> u16 {
self.chain_activity
}
}

/// A value that represents a value between 0 and 100. Higher values are clamped to 100
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, PartialOrd)]
pub struct ClampedPercentage {
value: u8,
}
Expand Down Expand Up @@ -226,6 +341,9 @@ impl AlgorithmUpdaterV1 {
let last_profit = rewards.saturating_sub(projected_total_da_cost);
self.update_last_profit(last_profit);

// activity
self.update_activity(used, capacity);

// gas prices
self.update_exec_gas_price(used, capacity);
self.update_da_gas_price();
Expand All @@ -236,6 +354,12 @@ impl AlgorithmUpdaterV1 {
}
}

fn update_activity(&mut self, used: u64, capacity: NonZeroU64) {
let block_activity = used.saturating_mul(100).div(capacity);
let usage = ClampedPercentage::new(block_activity.try_into().unwrap_or(100));
self.l2_activity.update(usage);
}

fn update_da_rewards(&mut self, fee_wei: u128) {
let block_da_reward = self.da_portion_of_fee(fee_wei);
self.total_da_rewards_excess =
Expand Down Expand Up @@ -309,7 +433,8 @@ impl AlgorithmUpdaterV1 {
fn update_da_gas_price(&mut self) {
let p = self.p();
let d = self.d();
let da_change = self.da_change(p, d);
let maybe_da_change = self.da_change(p, d);
let da_change = self.da_change_accounting_for_activity(maybe_da_change);
let maybe_new_scaled_da_gas_price = i128::from(self.new_scaled_da_gas_price)
.checked_add(da_change)
.and_then(|x| u64::try_from(x).ok())
Expand All @@ -326,6 +451,18 @@ impl AlgorithmUpdaterV1 {
);
}

fn da_change_accounting_for_activity(&self, maybe_da_change: i128) -> i128 {
if maybe_da_change > 0 {
match self.l2_activity.safety_mode() {
DAGasPriceSafetyMode::Normal => maybe_da_change,
DAGasPriceSafetyMode::Hold => 0,
DAGasPriceSafetyMode::Decrease => self.max_change().saturating_mul(-1),
rafal-ch marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
maybe_da_change
}
}

fn min_scaled_da_gas_price(&self) -> u64 {
self.min_da_gas_price
.saturating_mul(self.gas_price_factor.into())
Expand All @@ -348,14 +485,18 @@ impl AlgorithmUpdaterV1 {

fn da_change(&self, p: i128, d: i128) -> i128 {
let pd_change = p.saturating_add(d);
let max_change = self.max_change();
let clamped_change = pd_change.saturating_abs().min(max_change);
pd_change.signum().saturating_mul(clamped_change)
}

// Should always be positive
fn max_change(&self) -> i128 {
let upcast_percent = self.max_da_gas_price_change_percent.into();
let max_change = self
.new_scaled_da_gas_price
self.new_scaled_da_gas_price
.saturating_mul(upcast_percent)
.saturating_div(100)
.into();
let clamped_change = pd_change.saturating_abs().min(max_change);
pd_change.signum().saturating_mul(clamped_change)
.into()
}

fn exec_change(&self, principle: u64) -> u64 {
Expand Down
13 changes: 12 additions & 1 deletion crates/fuel-gas-price-algorithm/src/v1/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
#![allow(clippy::arithmetic_side_effects)]
#![allow(clippy::cast_possible_truncation)]

use crate::v1::AlgorithmUpdaterV1;
use crate::v1::{
AlgorithmUpdaterV1,
L2ActivityTracker,
};

#[cfg(test)]
mod algorithm_v1_tests;
Expand Down Expand Up @@ -40,6 +43,7 @@ pub struct UpdaterBuilder {
last_profit: i128,
second_to_last_profit: i128,
da_gas_price_factor: u64,
l2_activity: L2ActivityTracker,
}

impl UpdaterBuilder {
Expand Down Expand Up @@ -67,6 +71,7 @@ impl UpdaterBuilder {
last_profit: 0,
second_to_last_profit: 0,
da_gas_price_factor: 1,
l2_activity: L2ActivityTracker::new_always_normal(),
}
}

Expand Down Expand Up @@ -159,6 +164,11 @@ impl UpdaterBuilder {
self
}

fn with_activity(mut self, l2_activity: L2ActivityTracker) -> Self {
self.l2_activity = l2_activity;
self
}

fn build(self) -> AlgorithmUpdaterV1 {
AlgorithmUpdaterV1 {
min_exec_gas_price: self.min_exec_gas_price,
Expand Down Expand Up @@ -190,6 +200,7 @@ impl UpdaterBuilder {
.da_gas_price_factor
.try_into()
.expect("Should never be non-zero"),
l2_activity: self.l2_activity,
}
}
}
Expand Down
Loading
Loading