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

Use bytes from unrecorded_blocks rather from the block from DA #2252

Merged
merged 22 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ffa4fdb
Fix DA tests to expect unrecorded blocks to exist and to take the byt…
MitchTurner Sep 25, 2024
e82170c
Merge branch 'master' into fix/use-bytes-from-l2-blocks-not-da
MitchTurner Sep 25, 2024
fc3cf91
Remove regression for unimportant failure mode
MitchTurner Sep 25, 2024
8cd972f
Move struct that is only used in tests
MitchTurner Sep 25, 2024
8fd6616
Remove unused struct
MitchTurner Sep 26, 2024
49e521e
Change interface of `update_da_record_data`
MitchTurner Sep 26, 2024
cc28922
Move `RecordedBlock` into tests
MitchTurner Sep 26, 2024
e00319c
Merge branch 'master' into fix/use-bytes-from-l2-blocks-not-da
MitchTurner Sep 26, 2024
93a6e98
Use BTreeMap instead of HashMap
MitchTurner Sep 26, 2024
41c26f0
Use `pop_first` instead of `remove`
MitchTurner Sep 26, 2024
364ce4a
Fix compilation errors
MitchTurner Sep 26, 2024
e40a857
Kinda fix the analyzer I think
MitchTurner Sep 26, 2024
ea5469f
Cleanup prints
MitchTurner Sep 26, 2024
6a845ba
Merge branch 'master' into fix/use-bytes-from-l2-blocks-not-da
MitchTurner Sep 27, 2024
bfafd03
Fix test compilation
MitchTurner Sep 27, 2024
97fcc32
Fix profit chart length
MitchTurner Sep 27, 2024
a51a32c
Remove normalization function to fix simulation
MitchTurner Sep 27, 2024
dd8a252
Remove comment, add todo
MitchTurner Sep 30, 2024
7880500
Merge branch 'master' into fix/use-bytes-from-l2-blocks-not-da
MitchTurner Oct 1, 2024
76b904a
Merge remote-tracking branch 'origin' into fix/use-bytes-from-l2-bloc…
MitchTurner Oct 2, 2024
5848462
clean up function signature
MitchTurner Oct 2, 2024
8ec9c18
revert file inclusion from botched merge
MitchTurner Oct 2, 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
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,10 @@ pub fn draw_profit(
.x_label_area_size(40)
.y_label_area_size(100)
.right_y_label_area_size(100)
.build_cartesian_2d(0..actual_profit_gwei.len(), min..max)
.build_cartesian_2d(0..projected_profit.len(), min..max)
.unwrap()
.set_secondary_coord(
0..actual_profit_gwei.len(),
0..projected_profit.len(),
0..*pessimistic_block_costs_gwei.iter().max().unwrap(),
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use fuel_gas_price_algorithm::v1::{
AlgorithmUpdaterV1,
RecordedBlock,
};
use std::num::NonZeroU64;

use super::*;
use fuel_gas_price_algorithm::v1::AlgorithmUpdaterV1;
use std::{
collections::BTreeMap,
num::NonZeroU64,
ops::Range,
};

pub mod da_cost_per_byte;

Expand Down Expand Up @@ -90,7 +90,7 @@ impl Simulator {
latest_da_cost_per_byte: 0,
projected_total_da_cost: 0,
latest_known_total_da_cost_excess: 0,
unrecorded_blocks: vec![],
unrecorded_blocks: BTreeMap::new(),
da_p_component,
da_d_component,
last_profit: 0,
Expand All @@ -104,8 +104,7 @@ impl Simulator {
capacity: u64,
max_block_bytes: u64,
fullness_and_bytes: Vec<(u64, u64)>,
// blocks: Enumerate<Zip<Iter<(u64, u64)>, Iter<Option<Vec<RecordedBlock>>>>>,
blocks: impl Iterator<Item = (usize, ((u64, u64), &'a Option<Vec<RecordedBlock>>))>,
blocks: impl Iterator<Item = (usize, ((u64, u64), &'a Option<(Range<u32>, u128)>))>,
mut updater: AlgorithmUpdaterV1,
Copy link
Member

Choose a reason for hiding this comment

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

nit: this type seems complicated enough that a named type should make it more readable

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

) -> SimulationResults {
let mut gas_prices = vec![];
Expand All @@ -121,7 +120,7 @@ impl Simulator {
da_gas_prices.push(updater.new_scaled_da_gas_price);
let gas_price = updater.algorithm().calculate();
gas_prices.push(gas_price);
let total_fee = gas_price * fullness;
let total_fee = gas_price as u128 * fullness as u128;
updater
.update_l2_block_data(
height,
Expand All @@ -137,13 +136,13 @@ impl Simulator {
projected_cost_totals.push(updater.projected_total_da_cost);

// Update DA blocks on the occasion there is one
if let Some(da_blocks) = &da_block {
let mut total_cost = updater.latest_known_total_da_cost_excess;
for block in da_blocks {
total_cost += block.block_cost as u128;
actual_costs.push(total_cost);
if let Some((range, cost)) = da_block {
for height in range.to_owned() {
updater
.update_da_record_data(height..(height + 1), *cost)
.unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.update_da_record_data(height..(height + 1), *cost)
.update_da_record_data(height..=height, *cost)

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately, RangeInclusive isn't the same type as Range, I went back and forth on whether to do a generic for the few traits I needed or just do Range and I figured this was less noise.

Copy link
Member Author

Choose a reason for hiding this comment

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

Willing to reconsider if generics seems better.

actual_costs.push(updater.latest_known_total_da_cost_excess)
}
updater.update_da_record_data(&da_blocks).unwrap();
}
}
let (fullness_without_capacity, bytes): (Vec<_>, Vec<_>) =
Expand Down Expand Up @@ -187,7 +186,7 @@ impl Simulator {
da_recording_rate: usize,
da_finalization_rate: usize,
fullness_and_bytes: &Vec<(u64, u64)>,
) -> Vec<Option<Vec<RecordedBlock>>> {
) -> Vec<Option<(Range<u32>, u128)>> {
let l2_blocks_with_no_da_blocks =
std::iter::repeat(None).take(da_finalization_rate);
let (_, da_blocks) = fullness_and_bytes
Expand All @@ -199,12 +198,9 @@ impl Simulator {
|(mut delayed, mut recorded),
(index, ((_fullness, bytes), cost_per_byte))| {
let total_cost = *bytes * cost_per_byte;

let height = index as u32 + 1;
let converted = RecordedBlock {
height,
block_bytes: *bytes,
block_cost: total_cost as u64,
};
let converted = (height, bytes, total_cost);
delayed.push(converted);
if delayed.len() == da_recording_rate {
recorded.push(Some(delayed));
Expand All @@ -215,7 +211,16 @@ impl Simulator {
}
},
);
l2_blocks_with_no_da_blocks.chain(da_blocks).collect()
let da_block_ranges = da_blocks.into_iter().map(|maybe_recorded_blocks| {
maybe_recorded_blocks.map(|list| {
let heights_iter = list.iter().map(|(height, _, _)| *height);
let min = heights_iter.clone().min().unwrap();
let max = heights_iter.max().unwrap();
let cost: u128 = list.iter().map(|(_, _, cost)| *cost as u128).sum();
rafal-ch marked this conversation as resolved.
Show resolved Hide resolved
(min..(max + 1), cost)
})
});
l2_blocks_with_no_da_blocks.chain(da_block_ranges).collect()
}
}

Expand Down
6 changes: 0 additions & 6 deletions crates/fuel-gas-price-algorithm/src/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,6 @@ pub struct AlgorithmUpdaterV0 {
pub l2_block_fullness_threshold_percent: u64,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
pub struct BlockBytes {
pub height: u32,
pub block_bytes: u64,
}

impl AlgorithmUpdaterV0 {
pub fn new(
new_exec_price: u64,
Expand Down
128 changes: 71 additions & 57 deletions crates/fuel-gas-price-algorithm/src/v1.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::utils::cumulative_percentage_change;
use std::{
cmp::max,
collections::BTreeMap,
num::NonZeroU64,
ops::Div,
ops::{
Div,
Range,
},
};

use crate::utils::cumulative_percentage_change;

#[cfg(test)]
mod tests;

Expand All @@ -16,9 +19,11 @@ pub enum Error {
#[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: u64, cost: u64 },
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),
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -94,6 +99,8 @@ impl AlgorithmV1 {
/// The DA portion also uses a moving average of the profits over the last `avg_window` blocks
/// instead of the actual profit. Setting the `avg_window` to 1 will effectively disable the
/// moving average.
type Height = u32;
type Bytes = u64;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
pub struct AlgorithmUpdaterV1 {
// Execution
Expand Down Expand Up @@ -144,8 +151,9 @@ 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,

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

/// A value that represents a value between 0 and 100. Higher values are clamped to 100
Expand Down Expand Up @@ -176,29 +184,18 @@ impl core::ops::Deref for ClampedPercentage {
}
}

#[derive(Debug, Clone)]
pub struct RecordedBlock {
pub height: u32,
pub block_bytes: u64,
pub block_cost: u64,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
pub struct BlockBytes {
pub height: u32,
pub block_bytes: u64,
}

impl AlgorithmUpdaterV1 {
pub fn update_da_record_data(
&mut self,
blocks: &[RecordedBlock],
height_range: Range<u32>,
range_cost: u128,
) -> Result<(), Error> {
for block in blocks {
self.da_block_update(block.height, block.block_bytes, block.block_cost)?;
if !height_range.is_empty() {
self.da_block_update(height_range, range_cost)?;
self.recalculate_projected_cost();
// TODO: https://github.com/FuelLabs/fuel-core/issues/2264
// self.normalize_rewards_and_costs();
Copy link
Member

Choose a reason for hiding this comment

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

Is this commented-out on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. I added the TODO with the related issue.

}
self.recalculate_projected_cost();
self.normalize_rewards_and_costs();
Ok(())
}

Expand All @@ -208,7 +205,7 @@ impl AlgorithmUpdaterV1 {
used: u64,
capacity: NonZeroU64,
block_bytes: u64,
fee_wei: u64,
fee_wei: u128,
) -> Result<(), Error> {
let expected = self.l2_block_height.saturating_add(1);
if height != expected {
Expand All @@ -226,6 +223,8 @@ impl AlgorithmUpdaterV1 {
// costs
self.update_projected_cost(block_bytes);
let projected_total_da_cost = self.clamped_projected_cost_as_i128();

// profit
let last_profit = rewards.saturating_sub(projected_total_da_cost);
self.update_last_profit(last_profit);

Expand All @@ -234,19 +233,15 @@ impl AlgorithmUpdaterV1 {
self.update_da_gas_price();

// metadata
self.unrecorded_blocks.push(BlockBytes {
height,
block_bytes,
});
self.unrecorded_blocks.insert(height, block_bytes);
Ok(())
}
}

fn update_rewards(&mut self, fee_wei: u64) {
fn update_rewards(&mut self, fee_wei: u128) {
let block_da_reward = self.da_portion_of_fee(fee_wei);
self.total_da_rewards_excess = self
.total_da_rewards_excess
.saturating_add(block_da_reward.into());
self.total_da_rewards_excess =
self.total_da_rewards_excess.saturating_add(block_da_reward);
}

fn update_projected_cost(&mut self, block_bytes: u64) {
Expand All @@ -258,12 +253,11 @@ impl AlgorithmUpdaterV1 {
}

// Take the `fee_wei` and return the portion of the fee that should be used for paying DA costs
fn da_portion_of_fee(&self, fee_wei: u64) -> u64 {
fn da_portion_of_fee(&self, fee_wei: u128) -> u128 {
// fee_wei * (da_price / (exec_price + da_price))
let numerator = fee_wei.saturating_mul(self.descaled_da_price());
let denominator = self
.descaled_exec_price()
.saturating_add(self.descaled_da_price());
let numerator = fee_wei.saturating_mul(self.descaled_da_price() as u128);
let denominator = (self.descaled_exec_price() as u128)
.saturating_add(self.descaled_da_price() as u128);
if denominator == 0 {
0
} else {
Expand Down Expand Up @@ -374,43 +368,60 @@ impl AlgorithmUpdaterV1 {

fn da_block_update(
&mut self,
height: u32,
block_bytes: u64,
block_cost: u64,
height_range: Range<u32>,
range_cost: u128,
) -> Result<(), Error> {
let expected = self.da_recorded_block_height.saturating_add(1);
if height != expected {
let first = height_range.start;
if first != expected {
Err(Error::SkippedDABlock {
expected: self.da_recorded_block_height.saturating_add(1),
got: height,
expected,
got: first,
})
} else {
let new_cost_per_byte: u128 = (block_cost as u128)
.checked_div(block_bytes as u128)
.ok_or(Error::CouldNotCalculateCostPerByte {
bytes: block_bytes,
cost: block_cost,
})?;
self.da_recorded_block_height = height;
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_block_cost = self
.latest_known_total_da_cost_excess
.saturating_add(block_cost as u128);
.saturating_add(range_cost);
self.latest_known_total_da_cost_excess = new_block_cost;
self.latest_da_cost_per_byte = new_cost_per_byte;
Ok(())
}
}

fn drain_l2_block_bytes_for_range(
Copy link
Member Author

@MitchTurner MitchTurner Sep 26, 2024

Choose a reason for hiding this comment

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

So, we can consider improving performance here still.

Copy link
Member Author

@MitchTurner MitchTurner Sep 26, 2024

Choose a reason for hiding this comment

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

Switched to BTreeMap and pop_first.

&mut self,
height_range: Range<u32>,
) -> Result<u128, Error> {
let mut total: u128 = 0;
for expected_height in height_range {
let (actual_height, bytes) = self
.unrecorded_blocks
.pop_first()
rafal-ch marked this conversation as resolved.
Show resolved Hide resolved
.ok_or(Error::L2BlockExpectedNotFound(expected_height))?;
if actual_height != expected_height {
return Err(Error::L2BlockExpectedNotFound(expected_height));
}
total = total.saturating_add(bytes as u128);
}
Ok(total)
}

fn recalculate_projected_cost(&mut self) {
// remove all blocks that have been recorded
self.unrecorded_blocks
.retain(|block| block.height > self.da_recorded_block_height);
// add the cost of the remaining blocks
let projection_portion: u128 = self
.unrecorded_blocks
.iter()
.map(|block| {
(block.block_bytes as u128).saturating_mul(self.latest_da_cost_per_byte)
.map(|(_, &bytes)| {
(bytes as u128).saturating_mul(self.latest_da_cost_per_byte)
})
.sum();
self.projected_total_da_cost = self
Expand All @@ -436,9 +447,12 @@ impl AlgorithmUpdaterV1 {
}
}

// TODO: This breaks our simulation now that we are using an extended finalization period. We
// can either keep this function and fix the simulation, or we might decide to remove it.
// https://github.com/FuelLabs/fuel-core/issues/2264
// We only need to track the difference between the rewards and costs after we have true DA data
// Normalize, or zero out the lower value and subtract it from the higher value
fn normalize_rewards_and_costs(&mut self) {
fn _normalize_rewards_and_costs(&mut self) {
let (excess, projected_cost_excess) =
if self.total_da_rewards_excess > self.latest_known_total_da_cost_excess {
(
Expand Down
Loading
Loading