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

Allow DA recorded blocks to come out-of-order #2415

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
448d8c2
Remove height parameter, change tests
MitchTurner Oct 30, 2024
c5866a3
Modify to take vec instead of range
MitchTurner Oct 30, 2024
c54f098
Add tests to make sure it is sane
MitchTurner Oct 30, 2024
fcf4477
Rename variable
MitchTurner Oct 30, 2024
159d5b4
Fix analyzer
MitchTurner Oct 30, 2024
ba080ff
Rename some params
MitchTurner Oct 30, 2024
d2b3c28
Use slice instead of vec
MitchTurner Oct 30, 2024
128a9ba
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Oct 30, 2024
5f19932
Cleanup code a bit
MitchTurner Oct 31, 2024
922f3e0
Update crates/fuel-gas-price-algorithm/src/v1/tests/update_da_record_…
MitchTurner Oct 31, 2024
d7e9bdd
Pick nit
MitchTurner Oct 31, 2024
12c517e
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Oct 31, 2024
f8dca06
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Oct 31, 2024
2bb86fe
Optimize projected calc by caching running total
MitchTurner Nov 1, 2024
60771e9
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Nov 1, 2024
03da963
Fix analyzer
MitchTurner Nov 1, 2024
ea14e6f
Simplify metadata
MitchTurner Nov 1, 2024
b615b33
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Nov 14, 2024
cfd1a71
Merge remote-tracking branch 'origin' into feature/allow-DA-blocks-to…
MitchTurner Nov 17, 2024
d983891
Resolve logical conflicts from merge
MitchTurner Nov 17, 2024
4c2b844
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Nov 17, 2024
48fe978
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Nov 18, 2024
b578a8e
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Nov 19, 2024
7ca55c7
Ignore entire batches with blocks not in the unrecorded_blocks
MitchTurner Nov 19, 2024
6ac41de
Add comments to explain unexpected behavior
MitchTurner Nov 19, 2024
f2af4aa
Merge branch 'master' into feature/allow-DA-blocks-to-be-received-out…
MitchTurner Nov 20, 2024
cc90cbd
Fix tracing messages to be emitted at the right time
MitchTurner Nov 20, 2024
7ac01aa
Fix logic to always remove recorded blocks, use predicted cost as bes…
MitchTurner Nov 20, 2024
9f78fa6
Add tests, fix bad bug
MitchTurner Nov 20, 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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/fuel-gas-price-algorithm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ path = "src/lib.rs"
[dependencies]
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
proptest = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,14 @@ pub fn draw_bytes_and_cost_per_block(
Ok(())
}

fn one_gwei_i128() -> i128 {
i128::from(ONE_GWEI)
}

fn one_gwei_u128() -> u128 {
u128::from(ONE_GWEI)
}

pub fn draw_profit(
drawing_area: &DrawingArea<BitMapBackend, Shift>,
actual_profit: &[i128],
Expand All @@ -267,14 +275,14 @@ pub fn draw_profit(
const PROJECTED_PROFIT_COLOR: RGBColor = RED;
const PESSIMISTIC_BLOCK_COST_COLOR: RGBColor = BLUE;
let actual_profit_gwei: Vec<_> =
actual_profit.iter().map(|x| x / ONE_GWEI as i128).collect();
actual_profit.iter().map(|x| x / one_gwei_i128()).collect();
let projected_profit_gwei: Vec<_> = projected_profit
.iter()
.map(|x| x / ONE_GWEI as i128)
.map(|x| x / one_gwei_i128())
.collect();
let pessimistic_block_costs_gwei: Vec<_> = pessimistic_block_costs
.iter()
.map(|x| x / ONE_GWEI as u128)
.map(|x| x / one_gwei_u128())
.collect();
let min = *std::cmp::min(
actual_profit_gwei
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ impl Simulator {
// Increase to make the da price change faster
max_da_gas_price_change_percent: 10,
total_da_rewards_excess: 0,
da_recorded_block_height: 0,
// Change to adjust the cost per byte of the DA on block 0
latest_da_cost_per_byte: 0,
projected_total_da_cost: 0,
Expand All @@ -114,6 +113,7 @@ impl Simulator {
last_profit: 0,
second_to_last_profit: 0,
l2_activity: always_normal_activity,
unrecorded_blocks_bytes: 0,
}
}

Expand Down Expand Up @@ -160,10 +160,9 @@ impl Simulator {

// Update DA blocks on the occasion there is one
if let Some((range, cost)) = da_block {
for height in range {
updater
.update_da_record_data(height..(height + 1), cost)
.unwrap();
for height in range.to_owned() {
let block_heights: Vec<u32> = (height..(height) + 1).collect();
updater.update_da_record_data(&block_heights, cost).unwrap();
actual_costs.push(updater.latest_known_total_da_cost_excess)
}
}
Expand Down
140 changes: 80 additions & 60 deletions crates/fuel-gas-price-algorithm/src/v1.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::utils::cumulative_percentage_change;
use crate::{
utils::cumulative_percentage_change,
v1::BytesForHeights::{
Complete,
Incomplete,
},
};
use std::{
cmp::max,
collections::BTreeMap,
num::NonZeroU64,
ops::{
Div,
Range,
},
ops::Div,
};

#[cfg(test)]
Expand All @@ -16,14 +19,12 @@ mod tests;
pub enum Error {
#[error("Skipped L2 block update: expected {expected:?}, got {got:?}")]
SkippedL2Block { expected: u32, got: u32 },
#[error("Skipped DA block update: expected {expected:?}, got {got:?}")]
SkippedDABlock { expected: u32, got: u32 },
#[error("Could not calculate cost per byte: {bytes:?} bytes, {cost:?} cost")]
CouldNotCalculateCostPerByte { bytes: u128, cost: u128 },
#[error("Failed to include L2 block data: {0}")]
FailedTooIncludeL2BlockData(String),
#[error("L2 block expected but not found in unrecorded blocks: {0}")]
L2BlockExpectedNotFound(u32),
#[error("L2 block expected but not found in unrecorded blocks: {height}")]
L2BlockExpectedNotFound { height: u32 },
}

// TODO: separate exec gas price and DA gas price into newtypes for clarity
Expand Down Expand Up @@ -131,8 +132,6 @@ pub struct AlgorithmUpdaterV1 {
pub max_da_gas_price_change_percent: u16,
/// The cumulative reward from the DA portion of the gas price
pub total_da_rewards_excess: u128,
/// The height of the last L2 block recorded on the DA chain
pub da_recorded_block_height: u32,
/// The cumulative cost of recording L2 blocks on the DA chain as of the last recorded block
pub latest_known_total_da_cost_excess: u128,
/// The predicted cost of recording L2 blocks on the DA chain as of the last L2 block
Expand All @@ -152,6 +151,8 @@ pub struct AlgorithmUpdaterV1 {
pub l2_activity: L2ActivityTracker,
/// The unrecorded blocks that are used to calculate the projected cost of recording blocks
pub unrecorded_blocks: BTreeMap<Height, Bytes>,
/// Total unrecorded block bytes
pub unrecorded_blocks_bytes: u128,
}

/// The `L2ActivityTracker` tracks the chain activity to determine a safety mode for setting the DA price.
Expand Down Expand Up @@ -304,14 +305,21 @@ impl core::ops::Deref for ClampedPercentage {
}
}

enum BytesForHeights {
// Was able to find all the heights
Complete(u128),
// Was not able to find all the heights
Incomplete(u128),
}

impl AlgorithmUpdaterV1 {
pub fn update_da_record_data(
&mut self,
height_range: Range<u32>,
range_cost: u128,
heights: &[u32],
recording_cost: u128,
netrome marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<(), Error> {
if !height_range.is_empty() {
self.da_block_update(height_range, range_cost)?;
if !heights.is_empty() {
self.da_block_update(heights, recording_cost)?;
self.recalculate_projected_cost();
self.update_da_gas_price();
}
Expand Down Expand Up @@ -356,6 +364,9 @@ impl AlgorithmUpdaterV1 {

// metadata
self.unrecorded_blocks.insert(height, block_bytes);
self.unrecorded_blocks_bytes = self
.unrecorded_blocks_bytes
.saturating_add(block_bytes as u128);
Ok(())
acerone85 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down Expand Up @@ -515,60 +526,69 @@ impl AlgorithmUpdaterV1 {

fn da_block_update(
&mut self,
height_range: Range<u32>,
range_cost: u128,
heights: &[u32],
recording_cost: u128,
) -> Result<(), Error> {
let expected = self.da_recorded_block_height.saturating_add(1);
let first = height_range.start;
if first != expected {
Err(Error::SkippedDABlock {
expected,
got: first,
})
} else {
let last = height_range.end.saturating_sub(1);
let range_bytes = self.drain_l2_block_bytes_for_range(height_range)?;
let new_cost_per_byte: u128 = range_cost.checked_div(range_bytes).ok_or(
Error::CouldNotCalculateCostPerByte {
bytes: range_bytes,
cost: range_cost,
},
)?;
self.da_recorded_block_height = last;
let new_da_block_cost = self
.latest_known_total_da_cost_excess
.saturating_add(range_cost);
self.latest_known_total_da_cost_excess = new_da_block_cost;
self.latest_da_cost_per_byte = new_cost_per_byte;
Ok(())
}
let bytes_for_heights = self.drain_l2_block_bytes_for_heights(heights);
match bytes_for_heights {
Complete(bytes) => {
let new_cost_per_byte: u128 = recording_cost.checked_div(bytes).ok_or(
Error::CouldNotCalculateCostPerByte {
bytes,
cost: recording_cost,
},
)?;
let new_da_block_cost = self
.latest_known_total_da_cost_excess
.saturating_add(recording_cost);
self.latest_known_total_da_cost_excess = new_da_block_cost;
self.latest_da_cost_per_byte = new_cost_per_byte;
}
// If we weren't able to get the bytes for all the heights, we can't update the cost per byte in a logical way.
// We will just accept the predicted cost as the real cost and move on with the algorithm
// We can safely say that the cost didn't exceed the `recording_cost`, so we will take the
// minimum of the two costs
Incomplete(bytes) => {
let new_guess_recording_cost = bytes
.saturating_mul(self.latest_da_cost_per_byte)
.min(recording_cost);
let new_da_block_cost = self
.latest_known_total_da_cost_excess
.saturating_add(new_guess_recording_cost);
self.latest_known_total_da_cost_excess = new_da_block_cost;
}
};
Ok(())
}

fn drain_l2_block_bytes_for_range(
&mut self,
height_range: Range<u32>,
) -> Result<u128, Error> {
// Get the bytes for all specified heights, or get none of them.
// Always remove the blocks from the unrecorded blocks so they don't build up indefinitely
fn drain_l2_block_bytes_for_heights(&mut self, heights: &[u32]) -> BytesForHeights {
let mut missed_some_heights = false;
let mut total: u128 = 0;
for expected_height in height_range {
let (actual_height, bytes) = self
.unrecorded_blocks
.pop_first()
.ok_or(Error::L2BlockExpectedNotFound(expected_height))?;
if actual_height != expected_height {
return Err(Error::L2BlockExpectedNotFound(expected_height));
for expected_height in heights {
let maybe_bytes = self.unrecorded_blocks.remove(expected_height);
if let Some(bytes) = maybe_bytes {
total = total.saturating_add(bytes as u128);
} else {
tracing::error!(
"L2 block expected but not found in unrecorded blocks: {}",
expected_height,
);
missed_some_heights = true;
}
total = total.saturating_add(bytes as u128);
}
Ok(total)
self.unrecorded_blocks_bytes = self.unrecorded_blocks_bytes.saturating_sub(total);
if missed_some_heights {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking about the following scenario, not sure if this is an issue or not:

  1. we have 3 blocks in self.unrecorded_blocks
  2. we remove() 2 of them, but we get an error on 3rd one
  3. now, the unrecorded_blocks_bytes becomes out-of-sync

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. This is the inverse of the problem I mentioned above.

Incomplete(total)
} else {
Complete(total)
}
}

fn recalculate_projected_cost(&mut self) {
// add the cost of the remaining blocks
let projection_portion: u128 = self
.unrecorded_blocks
.iter()
.map(|(_, &bytes)| (bytes as u128))
.fold(0_u128, |acc, n| acc.saturating_add(n))
let projection_portion = self
.unrecorded_blocks_bytes
.saturating_mul(self.latest_da_cost_per_byte);
self.projected_total_da_cost = self
.latest_known_total_da_cost_excess
Expand Down
12 changes: 4 additions & 8 deletions crates/fuel-gas-price-algorithm/src/v1/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ pub struct UpdaterBuilder {
l2_block_capacity_threshold: u8,

total_rewards: u128,
da_recorded_block_height: u32,
da_cost_per_byte: u128,
project_total_cost: u128,
latest_known_total_cost: u128,
Expand Down Expand Up @@ -63,7 +62,6 @@ impl UpdaterBuilder {
l2_block_capacity_threshold: 50,

total_rewards: 0,
da_recorded_block_height: 0,
da_cost_per_byte: 0,
project_total_cost: 0,
latest_known_total_cost: 0,
Expand Down Expand Up @@ -133,11 +131,6 @@ impl UpdaterBuilder {
self
}

fn with_da_recorded_block_height(mut self, da_recorded_block_height: u32) -> Self {
self.da_recorded_block_height = da_recorded_block_height;
self
}

fn with_da_cost_per_byte(mut self, da_cost_per_byte: u128) -> Self {
self.da_cost_per_byte = da_cost_per_byte;
self
Expand Down Expand Up @@ -184,7 +177,6 @@ impl UpdaterBuilder {
l2_block_fullness_threshold_percent: self.l2_block_capacity_threshold.into(),
total_da_rewards_excess: self.total_rewards,

da_recorded_block_height: self.da_recorded_block_height,
latest_da_cost_per_byte: self.da_cost_per_byte,
projected_total_da_cost: self.project_total_cost,
latest_known_total_da_cost_excess: self.latest_known_total_cost,
Expand All @@ -201,6 +193,10 @@ impl UpdaterBuilder {
.try_into()
.expect("Should never be non-zero"),
l2_activity: self.l2_activity,
unrecorded_blocks_bytes: self
.unrecorded_blocks
.iter()
.fold(0u128, |acc, b| acc + u128::from(b.block_bytes)),
}
}
}
Expand Down
Loading
Loading