diff --git a/crates/fuel-gas-price-algorithm/gas-price-analysis/src/charts.rs b/crates/fuel-gas-price-algorithm/gas-price-analysis/src/charts.rs index 32194a716a2..6b4c82f09ff 100644 --- a/crates/fuel-gas-price-algorithm/gas-price-analysis/src/charts.rs +++ b/crates/fuel-gas-price-algorithm/gas-price-analysis/src/charts.rs @@ -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(), ); diff --git a/crates/fuel-gas-price-algorithm/gas-price-analysis/src/simulation.rs b/crates/fuel-gas-price-algorithm/gas-price-analysis/src/simulation.rs index 6ad906cc41f..bbbc1529ad3 100644 --- a/crates/fuel-gas-price-algorithm/gas-price-analysis/src/simulation.rs +++ b/crates/fuel-gas-price-algorithm/gas-price-analysis/src/simulation.rs @@ -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; @@ -24,6 +24,13 @@ pub struct Simulator { da_cost_per_byte: Vec, } +// (usize, ((u64, u64), &'a Option<(Range, u128) +struct BlockData { + fullness: u64, + bytes: u64, + maybe_da_block: Option<(Range, u128)>, +} + impl Simulator { pub fn new(da_cost_per_byte: Vec) -> Self { Simulator { da_cost_per_byte } @@ -49,7 +56,14 @@ impl Simulator { &fullness_and_bytes, ); - let blocks = l2_blocks.zip(da_blocks.iter()).enumerate(); + let blocks = l2_blocks + .zip(da_blocks.iter()) + .map(|((fullness, bytes), maybe_da_block)| BlockData { + fullness, + bytes, + maybe_da_block: maybe_da_block.clone(), + }) + .enumerate(); let updater = self.build_updater(da_p_component, da_d_component); @@ -90,7 +104,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, @@ -104,8 +118,7 @@ impl Simulator { capacity: u64, max_block_bytes: u64, fullness_and_bytes: Vec<(u64, u64)>, - // blocks: Enumerate, Iter>>>>, - blocks: impl Iterator>))>, + blocks: impl Iterator, mut updater: AlgorithmUpdaterV1, ) -> SimulationResults { let mut gas_prices = vec![]; @@ -115,13 +128,18 @@ impl Simulator { let mut projected_cost_totals = vec![]; let mut actual_costs = vec![]; let mut pessimistic_costs = vec![]; - for (index, ((fullness, bytes), da_block)) in blocks { + for (index, block_data) in blocks { + let BlockData { + fullness, + bytes, + maybe_da_block: da_block, + } = block_data; let height = index as u32 + 1; exec_gas_prices.push(updater.new_scaled_exec_price); 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, @@ -137,13 +155,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(); + 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<_>) = @@ -187,7 +205,7 @@ impl Simulator { da_recording_rate: usize, da_finalization_rate: usize, fullness_and_bytes: &Vec<(u64, u64)>, - ) -> Vec>> { + ) -> Vec, u128)>> { let l2_blocks_with_no_da_blocks = std::iter::repeat(None).take(da_finalization_rate); let (_, da_blocks) = fullness_and_bytes @@ -199,12 +217,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)); @@ -215,7 +230,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(); + (min..(max + 1), cost) + }) + }); + l2_blocks_with_no_da_blocks.chain(da_block_ranges).collect() } } diff --git a/crates/fuel-gas-price-algorithm/src/v0.rs b/crates/fuel-gas-price-algorithm/src/v0.rs index 494c9045a88..418e6619c7e 100644 --- a/crates/fuel-gas-price-algorithm/src/v0.rs +++ b/crates/fuel-gas-price-algorithm/src/v0.rs @@ -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, diff --git a/crates/fuel-gas-price-algorithm/src/v1.rs b/crates/fuel-gas-price-algorithm/src/v1.rs index dad6d64beea..0496820f93a 100644 --- a/crates/fuel-gas-price-algorithm/src/v1.rs +++ b/crates/fuel-gas-price-algorithm/src/v1.rs @@ -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; @@ -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)] @@ -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 @@ -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, + pub unrecorded_blocks: BTreeMap, } /// A value that represents a value between 0 and 100. Higher values are clamped to 100 @@ -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, + 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(); } - self.recalculate_projected_cost(); - self.normalize_rewards_and_costs(); Ok(()) } @@ -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 { @@ -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); @@ -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) { @@ -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 { @@ -374,43 +368,60 @@ impl AlgorithmUpdaterV1 { fn da_block_update( &mut self, - height: u32, - block_bytes: u64, - block_cost: u64, + height_range: Range, + 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( + &mut self, + height_range: Range, + ) -> Result { + 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)); + } + 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 @@ -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 { ( diff --git a/crates/fuel-gas-price-algorithm/src/v1/tests.rs b/crates/fuel-gas-price-algorithm/src/v1/tests.rs index 707173baca4..0aca738a3f0 100644 --- a/crates/fuel-gas-price-algorithm/src/v1/tests.rs +++ b/crates/fuel-gas-price-algorithm/src/v1/tests.rs @@ -2,10 +2,7 @@ #![allow(clippy::arithmetic_side_effects)] #![allow(clippy::cast_possible_truncation)] -use crate::v1::{ - AlgorithmUpdaterV1, - BlockBytes, -}; +use crate::v1::AlgorithmUpdaterV1; #[cfg(test)] mod algorithm_v1_tests; @@ -14,6 +11,12 @@ mod update_da_record_data_tests; #[cfg(test)] mod update_l2_block_data_tests; +#[derive(Debug, Clone)] +pub struct BlockBytes { + pub height: u32, + pub block_bytes: u64, +} + pub struct UpdaterBuilder { min_exec_gas_price: u64, min_da_gas_price: u64, @@ -175,7 +178,11 @@ impl UpdaterBuilder { 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, - unrecorded_blocks: self.unrecorded_blocks, + unrecorded_blocks: self + .unrecorded_blocks + .iter() + .map(|b| (b.height, b.block_bytes)) + .collect(), last_profit: self.last_profit, second_to_last_profit: self.second_to_last_profit, min_da_gas_price: self.min_da_gas_price, diff --git a/crates/fuel-gas-price-algorithm/src/v1/tests/update_da_record_data_tests.rs b/crates/fuel-gas-price-algorithm/src/v1/tests/update_da_record_data_tests.rs index e9d64a63bcc..1c1628be60a 100644 --- a/crates/fuel-gas-price-algorithm/src/v1/tests/update_da_record_data_tests.rs +++ b/crates/fuel-gas-price-algorithm/src/v1/tests/update_da_record_data_tests.rs @@ -1,8 +1,9 @@ use crate::v1::{ - tests::UpdaterBuilder, - BlockBytes, + tests::{ + BlockBytes, + UpdaterBuilder, + }, Error, - RecordedBlock, }; use proptest::{ prelude::Rng, @@ -10,65 +11,64 @@ use proptest::{ proptest, }; use rand::SeedableRng; +use std::ops::Range; #[test] fn update_da_record_data__increases_block() { // given let da_recorded_block_height = 0; - let mut updater = UpdaterBuilder::new() - .with_da_recorded_block_height(da_recorded_block_height) - .build(); - - let blocks = vec![ - RecordedBlock { + let recorded_range = 1u32..3; + let recorded_cost = 200; + let unrecorded_blocks = vec![ + BlockBytes { height: 1, block_bytes: 1000, - block_cost: 100, }, - RecordedBlock { + BlockBytes { height: 2, - block_bytes: 1000, - block_cost: 100, + block_bytes: 2000, }, ]; + let mut updater = UpdaterBuilder::new() + .with_da_recorded_block_height(da_recorded_block_height) + .with_unrecorded_blocks(unrecorded_blocks) + .build(); // when - updater.update_da_record_data(&blocks).unwrap(); + updater + .update_da_record_data(recorded_range, recorded_cost) + .unwrap(); // then let expected = 2; let actual = updater.da_recorded_block_height; - assert_eq!(actual, expected); + assert_eq!(expected, actual); } #[test] fn update_da_record_data__throws_error_if_out_of_order() { // given let da_recorded_block_height = 0; + let bad_recorded_range = 2u32..4; + let recorded_cost = 200; + let unrecorded_blocks = vec![BlockBytes { + height: 1, + block_bytes: 1000, + }]; let mut updater = UpdaterBuilder::new() .with_da_recorded_block_height(da_recorded_block_height) + .with_unrecorded_blocks(unrecorded_blocks) .build(); - let blocks = vec![ - RecordedBlock { - height: 1, - block_bytes: 1000, - block_cost: 100, - }, - RecordedBlock { - height: 3, - block_bytes: 1000, - block_cost: 100, - }, - ]; - // when - let actual_error = updater.update_da_record_data(&blocks).unwrap_err(); + let actual_error = updater + .update_da_record_data(bad_recorded_range, recorded_cost) + .unwrap_err(); // then let expected_error = Error::SkippedDABlock { - expected: 2, - got: 3, + expected: 1, + got: 2, }; assert_eq!(actual_error, expected_error); } @@ -77,20 +77,23 @@ fn update_da_record_data__throws_error_if_out_of_order() { fn update_da_record_data__updates_cost_per_byte() { // given let da_cost_per_byte = 20; + let block_bytes = 1000; + let unrecorded_blocks = vec![BlockBytes { + height: 1, + block_bytes, + }]; let mut updater = UpdaterBuilder::new() .with_da_cost_per_byte(da_cost_per_byte) + .with_unrecorded_blocks(unrecorded_blocks) .build(); - let block_bytes = 1000; let new_cost_per_byte = 100; - let block_cost = block_bytes * new_cost_per_byte; - let blocks = vec![RecordedBlock { - height: 1, - block_bytes, - block_cost, - }]; + let recorded_cost = (block_bytes * new_cost_per_byte) as u128; + let recorded_range = 1u32..2; // when - updater.update_da_record_data(&blocks).unwrap(); + updater + .update_da_record_data(recorded_range, recorded_cost) + .unwrap(); // then let expected = new_cost_per_byte as u128; @@ -106,39 +109,39 @@ fn update_da_record_data__updates_known_total_cost() { let l2_block_height = 15; let projected_total_cost = 2000; let known_total_cost = 1500; + let unrecorded_blocks = vec![ + BlockBytes { + height: 11, + block_bytes: 1000, + }, + BlockBytes { + height: 12, + block_bytes: 2000, + }, + BlockBytes { + height: 13, + block_bytes: 1500, + }, + ]; let mut updater = UpdaterBuilder::new() .with_da_cost_per_byte(da_cost_per_byte) .with_da_recorded_block_height(da_recorded_block_height) .with_l2_block_height(l2_block_height) .with_projected_total_cost(projected_total_cost) .with_known_total_cost(known_total_cost) + .with_unrecorded_blocks(unrecorded_blocks) .build(); - let block_bytes = 1000; - let block_cost = 100; - let blocks = vec![ - RecordedBlock { - height: 11, - block_bytes, - block_cost, - }, - RecordedBlock { - height: 12, - block_bytes, - block_cost, - }, - RecordedBlock { - height: 13, - block_bytes, - block_cost, - }, - ]; + let recorded_range = 11u32..14; + let recorded_cost = 300; // when - updater.update_da_record_data(&blocks).unwrap(); + updater + .update_da_record_data(recorded_range, recorded_cost) + .unwrap(); // then let actual = updater.latest_known_total_da_cost_excess; - let expected = known_total_cost + (3 * block_cost as u128); + let expected = known_total_cost + recorded_cost; assert_eq!(actual, expected); } @@ -181,25 +184,13 @@ fn update_da_record_data__if_da_height_matches_l2_height_projected_and_known_mat let block_bytes = 1000; let new_cost_per_byte = 100; let block_cost = block_bytes * new_cost_per_byte; - let blocks = vec![ - RecordedBlock { - height: 11, - block_bytes, - block_cost, - }, - RecordedBlock { - height: 12, - block_bytes, - block_cost, - }, - RecordedBlock { - height: 13, - block_bytes, - block_cost, - }, - ]; + + let recorded_range = 11u32..14; + let recorded_cost = block_cost * 3; // when - updater.update_da_record_data(&blocks).unwrap(); + updater + .update_da_record_data(recorded_range, recorded_cost) + .unwrap(); // then assert_eq!(updater.l2_block_height, updater.da_recorded_block_height); @@ -216,7 +207,8 @@ fn update_da_record_data__da_block_updates_projected_total_cost_with_known_and_g let da_cost_per_byte = 20; let da_recorded_block_height = 10; let l2_block_height = 15; - let known_total_cost = 1500; + let original_known_total_cost = 1500; + let block_bytes = 1000; let mut unrecorded_blocks = vec![ BlockBytes { height: 11, @@ -235,54 +227,49 @@ fn update_da_record_data__da_block_updates_projected_total_cost_with_known_and_g let remaining = vec![ BlockBytes { height: 14, - block_bytes: 1200, + block_bytes, }, BlockBytes { height: 15, - block_bytes: 3000, + block_bytes, }, ]; + let to_be_removed = unrecorded_blocks.clone(); unrecorded_blocks.extend(remaining.clone()); let guessed_cost: u64 = unrecorded_blocks .iter() .map(|block| block.block_bytes * da_cost_per_byte) .sum(); - let projected_total_cost = known_total_cost + guessed_cost; + 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_recorded_block_height(da_recorded_block_height) .with_l2_block_height(l2_block_height) .with_projected_total_cost(projected_total_cost as u128) - .with_known_total_cost(known_total_cost as u128) + .with_known_total_cost(original_known_total_cost as u128) .with_unrecorded_blocks(unrecorded_blocks) .build(); - let block_bytes = 1000; let new_cost_per_byte = 100; - let block_cost = block_bytes * new_cost_per_byte; - let blocks = vec![ - RecordedBlock { - height: 11, - block_bytes, - block_cost, - }, - RecordedBlock { - height: 12, - block_bytes, - block_cost, - }, - RecordedBlock { - height: 13, - block_bytes, - block_cost, - }, - ]; + let (recorded_heights, recorded_cost) = + to_be_removed + .iter() + .fold((vec![], 0), |(mut range, cost), block| { + range.push(block.height); + (range, cost + block.block_bytes * new_cost_per_byte) + }); + let min = recorded_heights.iter().min().unwrap(); + let max = recorded_heights.iter().max().unwrap(); + let recorded_range = *min..(max + 1); + // when - updater.update_da_record_data(&blocks).unwrap(); + updater + .update_da_record_data(recorded_range, recorded_cost as u128) + .unwrap(); // then let actual = updater.projected_total_da_cost; - let new_known_total_cost = known_total_cost + 3 * block_cost; + let new_known_total_cost = original_known_total_cost + recorded_cost; let guessed_part: u64 = remaining .iter() .map(|block| block.block_bytes * new_cost_per_byte) @@ -291,41 +278,67 @@ fn update_da_record_data__da_block_updates_projected_total_cost_with_known_and_g assert_eq!(actual, expected as u128); } +#[derive(Debug, Clone)] +pub struct RecordedBlock { + pub height: u32, + pub block_cost: u64, +} + prop_compose! { - fn arb_vec_of_da_blocks()(last_da_block: u32, count in 1..123usize, rng_seed: u64) -> Vec { + fn arb_vec_of_da_blocks()(last_da_block: u32, count in 1..123usize, rng_seed: u64) -> (Vec, (Range, u128)) { let rng = &mut rand::rngs::StdRng::seed_from_u64(rng_seed); - let mut blocks = Vec::with_capacity(count); + let mut unrecorded_blocks = Vec::with_capacity(count); + let mut recorded_blocks = Vec::with_capacity(count); for i in 0..count { + let height = last_da_block + 1 + i as u32; let block_bytes = rng.gen_range(100..131_072); let cost_per_byte = rng.gen_range(1..1000000); let block_cost = block_bytes * cost_per_byte; - blocks.push(RecordedBlock { - height: last_da_block + 1 + i as u32, + unrecorded_blocks.push(BlockBytes { + height, block_bytes, + }); + recorded_blocks.push(RecordedBlock { + height, block_cost, }); } - blocks + let recorded_heights = recorded_blocks + .iter() + .map(|block| block.height) + .collect::>(); + let min = recorded_heights.iter().min().unwrap(); + let max = recorded_heights.iter().max().unwrap(); + let recorded_range = *min..(max + 1); + let recorded_cost = recorded_blocks + .iter() + .map(|block| block.block_cost as u128) + .sum(); + + (unrecorded_blocks, (recorded_range, recorded_cost)) } } prop_compose! { - fn reward_greater_than_cost_with_da_blocks()(cost: u64, extra: u64, blocks in arb_vec_of_da_blocks()) -> (u128, u128, Vec) { - let cost_from_blocks = blocks.iter().map(|block| block.block_cost as u128).sum::(); - let reward = cost as u128 + cost_from_blocks + extra as u128; - (cost as u128, reward, blocks) + fn reward_greater_than_cost_with_da_blocks()(cost: u64, extra: u64, (unrecorded_blocks, (recorded_range, recorded_cost)) in arb_vec_of_da_blocks()) -> (u128, u128, Vec, (Range, u128)) { + let reward = cost as u128 + recorded_cost + extra as u128; + (cost as u128, reward, unrecorded_blocks, (recorded_range, recorded_cost)) } } proptest! { + // https://github.com/FuelLabs/fuel-core/issues/2264 + #[ignore] #[test] fn update_da_record_data__when_reward_is_greater_than_cost_will_zero_cost_and_subtract_from_reward( - (cost, reward, blocks) in reward_greater_than_cost_with_da_blocks() + (cost, reward, unrecorded_blocks, (recorded_range, recorded_cost)) in reward_greater_than_cost_with_da_blocks() ) { _update_da_record_data__when_reward_is_greater_than_cost_will_zero_cost_and_subtract_from_reward( cost, reward, - blocks + unrecorded_blocks, + recorded_range, + recorded_cost ) } } @@ -333,11 +346,13 @@ proptest! { fn _update_da_record_data__when_reward_is_greater_than_cost_will_zero_cost_and_subtract_from_reward( known_total_cost: u128, total_rewards: u128, - blocks: Vec, + unrecorded_blocks: Vec, + recorded_range: Range, + recorded_cost: u128, ) { // given let da_cost_per_byte = 20; - let da_recorded_block_height = blocks.first().unwrap().height - 1; + let da_recorded_block_height = recorded_range.start - 1; let l2_block_height = 15; let mut updater = UpdaterBuilder::new() .with_da_cost_per_byte(da_cost_per_byte) @@ -345,15 +360,16 @@ fn _update_da_record_data__when_reward_is_greater_than_cost_will_zero_cost_and_s .with_l2_block_height(l2_block_height) .with_known_total_cost(known_total_cost) .with_total_rewards(total_rewards) + .with_unrecorded_blocks(unrecorded_blocks) .build(); - let new_costs = blocks.iter().map(|block| block.block_cost).sum::(); - // when - updater.update_da_record_data(&blocks).unwrap(); + updater + .update_da_record_data(recorded_range, recorded_cost) + .unwrap(); // then - let expected = total_rewards - new_costs as u128 - known_total_cost; + let expected = total_rewards - recorded_cost - known_total_cost; let actual = updater.total_da_rewards_excess; assert_eq!(actual, expected); @@ -363,22 +379,25 @@ fn _update_da_record_data__when_reward_is_greater_than_cost_will_zero_cost_and_s } prop_compose! { - fn cost_greater_than_reward_with_da_blocks()(reward: u64, extra: u64, blocks in arb_vec_of_da_blocks()) -> (u128, u128, Vec) { - let cost_from_blocks = blocks.iter().map(|block| block.block_cost as u128).sum::(); - let cost = reward as u128 + cost_from_blocks + extra as u128; - (cost, reward as u128, blocks) + fn cost_greater_than_reward_with_da_blocks()(reward: u64, extra: u64, (unrecorded_blocks, (recorded_range, recorded_cost)) in arb_vec_of_da_blocks()) -> (u128, u128, Vec, (Range, u128)) { + let cost = reward as u128 + recorded_cost + extra as u128; + (cost, reward as u128, unrecorded_blocks, (recorded_range, recorded_cost)) } } proptest! { + // https://github.com/FuelLabs/fuel-core/issues/2264 + #[ignore] #[test] fn update_da_record_data__when_cost_is_greater_than_reward_will_zero_reward_and_subtract_from_cost( - (cost, reward, blocks) in cost_greater_than_reward_with_da_blocks() + (cost, reward, unrecorded_blocks, (recorded_range, recorded_cost)) in cost_greater_than_reward_with_da_blocks() ) { _update_da_record_data__when_cost_is_greater_than_reward_will_zero_reward_and_subtract_from_cost( cost, reward, - blocks + unrecorded_blocks, + recorded_range, + recorded_cost ) } } @@ -386,11 +405,13 @@ proptest! { fn _update_da_record_data__when_cost_is_greater_than_reward_will_zero_reward_and_subtract_from_cost( known_total_cost: u128, total_rewards: u128, - blocks: Vec, + unrecorded_blocks: Vec, + recorded_range: Range, + recorded_cost: u128, ) { // given let da_cost_per_byte = 20; - let da_recorded_block_height = blocks.first().unwrap().height - 1; + let da_recorded_block_height = recorded_range.start - 1; let l2_block_height = 15; let mut updater = UpdaterBuilder::new() .with_da_cost_per_byte(da_cost_per_byte) @@ -398,19 +419,20 @@ fn _update_da_record_data__when_cost_is_greater_than_reward_will_zero_reward_and .with_l2_block_height(l2_block_height) .with_known_total_cost(known_total_cost) .with_total_rewards(total_rewards) + .with_unrecorded_blocks(unrecorded_blocks) .build(); - let new_costs = blocks.iter().map(|block| block.block_cost).sum::(); - // when - updater.update_da_record_data(&blocks).unwrap(); + updater + .update_da_record_data(recorded_range, recorded_cost) + .unwrap(); // then let expected = 0; let actual = updater.total_da_rewards_excess; assert_eq!(actual, expected); - let expected = known_total_cost + new_costs as u128 - total_rewards; + let expected = known_total_cost + recorded_cost - total_rewards; let actual = updater.latest_known_total_da_cost_excess; assert_eq!(actual, expected); } diff --git a/crates/fuel-gas-price-algorithm/src/v1/tests/update_l2_block_data_tests.rs b/crates/fuel-gas-price-algorithm/src/v1/tests/update_l2_block_data_tests.rs index 1ad9a1aacad..6d5e4cf2ce7 100644 --- a/crates/fuel-gas-price-algorithm/src/v1/tests/update_l2_block_data_tests.rs +++ b/crates/fuel-gas-price-algorithm/src/v1/tests/update_l2_block_data_tests.rs @@ -1,6 +1,8 @@ use crate::v1::{ - tests::UpdaterBuilder, - BlockBytes, + tests::{ + BlockBytes, + UpdaterBuilder, + }, Error, }; @@ -104,10 +106,10 @@ fn update_l2_block_data__updates_the_total_reward_value() { .unwrap(); // then - let expected = (fee * starting_da_gas_price) - .div_ceil(starting_da_gas_price + starting_exec_gas_price); + let expected = (fee * starting_da_gas_price as u128) + .div_ceil(starting_da_gas_price as u128 + starting_exec_gas_price as u128); let actual = updater.total_da_rewards_excess; - assert_eq!(actual, expected as u128); + assert_eq!(actual, expected); } #[test] @@ -480,7 +482,7 @@ fn update_l2_block_data__even_profit_maintains_price() { 50, 100.try_into().unwrap(), block_bytes, - total_fee, + total_fee.into(), ) .unwrap(); let algo = updater.algorithm(); @@ -563,8 +565,9 @@ fn update_l2_block_data__adds_l2_block_to_unrecorded_blocks() { height, block_bytes, }; - let contains_block_bytes = updater.unrecorded_blocks.contains(&block_bytes); - assert!(contains_block_bytes); + let expected = block_bytes.block_bytes; + let actual = updater.unrecorded_blocks.get(&block_bytes.height).unwrap(); + assert_eq!(expected, *actual); } #[test] @@ -598,10 +601,12 @@ fn update_l2_block_data__retains_existing_blocks_and_adds_l2_block_to_unrecorded height, block_bytes, }; - let contains_block_bytes = updater.unrecorded_blocks.contains(&block_bytes); + let contains_block_bytes = + updater.unrecorded_blocks.contains_key(&block_bytes.height); assert!(contains_block_bytes); - let contains_preexisting_block_bytes = - updater.unrecorded_blocks.contains(&preexisting_block); + let contains_preexisting_block_bytes = updater + .unrecorded_blocks + .contains_key(&preexisting_block.height); assert!(contains_preexisting_block_bytes); }