diff --git a/crates/fuel-core/src/database.rs b/crates/fuel-core/src/database.rs index ac479fae6f8..851ebd08b2a 100644 --- a/crates/fuel-core/src/database.rs +++ b/crates/fuel-core/src/database.rs @@ -65,6 +65,7 @@ pub use fuel_core_database::Error; pub type Result = core::result::Result; // TODO: Extract `Database` and all belongs into `fuel-core-database`. +use crate::database::database_description::gas_price::GasPriceDatabase; #[cfg(feature = "rocksdb")] use crate::state::{ historical_rocksdb::{ @@ -74,10 +75,9 @@ use crate::state::{ }, rocks_db::RocksDb, }; +use fuel_core_gas_price_service::common::fuel_core_storage_adapter::storage::GasPriceMetadata; #[cfg(feature = "rocksdb")] use std::path::Path; -use fuel_core_gas_price_service::fuel_gas_price_updater::fuel_core_storage_adapter::storage::GasPriceMetadata; -use crate::database::database_description::gas_price::GasPriceDatabase; // Storages implementation pub mod balances; diff --git a/crates/fuel-core/src/database/database_description/gas_price.rs b/crates/fuel-core/src/database/database_description/gas_price.rs index 9487f0cf06f..66d015b46a8 100644 --- a/crates/fuel-core/src/database/database_description/gas_price.rs +++ b/crates/fuel-core/src/database/database_description/gas_price.rs @@ -1,6 +1,6 @@ -use fuel_core_gas_price_service::fuel_gas_price_updater::fuel_core_storage_adapter::storage::GasPriceColumn; -use fuel_core_types::fuel_types::BlockHeight; use crate::database::database_description::DatabaseDescription; +use fuel_core_gas_price_service::common::fuel_core_storage_adapter::storage::GasPriceColumn; +use fuel_core_types::fuel_types::BlockHeight; #[derive(Clone, Copy, Debug)] pub struct GasPriceDatabase; diff --git a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider.rs b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider.rs index 89488e29c23..c1d79e3d1f3 100644 --- a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider.rs +++ b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider.rs @@ -1,8 +1,9 @@ use crate::fuel_core_graphql_api::ports::GasPriceEstimate as GraphqlGasPriceEstimate; -use fuel_core_gas_price_service::{ +use fuel_core_gas_price_service::common::gas_price_algorithm::{ GasPriceAlgorithm, SharedGasPriceAlgo, }; + use fuel_core_producer::block_producer::gas_price::GasPriceProvider as ProducerGasPriceProvider; use fuel_core_txpool::{ ports::GasPriceProvider as TxPoolGasPriceProvider, diff --git a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/producer_gas_price_tests.rs b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/producer_gas_price_tests.rs index 9c1c9d3aad3..81429785248 100644 --- a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/producer_gas_price_tests.rs +++ b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/producer_gas_price_tests.rs @@ -1,7 +1,7 @@ use crate::service::adapters::fuel_gas_price_provider::tests::build_provider; use fuel_core_gas_price_service::{ + common::gas_price_algorithm::GasPriceAlgorithm, static_updater::StaticAlgorithm, - GasPriceAlgorithm, }; #[tokio::test] diff --git a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/tx_pool_gas_price_tests.rs b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/tx_pool_gas_price_tests.rs index 9c1c9d3aad3..81429785248 100644 --- a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/tx_pool_gas_price_tests.rs +++ b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/tx_pool_gas_price_tests.rs @@ -1,7 +1,7 @@ use crate::service::adapters::fuel_gas_price_provider::tests::build_provider; use fuel_core_gas_price_service::{ + common::gas_price_algorithm::GasPriceAlgorithm, static_updater::StaticAlgorithm, - GasPriceAlgorithm, }; #[tokio::test] diff --git a/crates/fuel-core/src/service/adapters/gas_price_adapters.rs b/crates/fuel-core/src/service/adapters/gas_price_adapters.rs index 34eeb6dbe36..014dc98456a 100644 --- a/crates/fuel-core/src/service/adapters/gas_price_adapters.rs +++ b/crates/fuel-core/src/service/adapters/gas_price_adapters.rs @@ -3,13 +3,15 @@ use crate::{ service::adapters::ConsensusParametersProvider, }; use fuel_core_gas_price_service::{ - fuel_gas_price_updater::{ + common::{ fuel_core_storage_adapter::{ GasPriceSettings, GasPriceSettingsProvider, }, - Error as GasPriceError, - Result as GasPriceResult, + utils::{ + Error as GasPriceError, + Result as GasPriceResult, + }, }, ports::{ GasPriceData, diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index ea3e62a9cd6..93686950f21 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -38,24 +38,14 @@ use crate::{ SubServices, }, }; -#[allow(unused_imports)] -use fuel_core_gas_price_service::fuel_gas_price_updater::{ - algorithm_updater, - fuel_core_storage_adapter::FuelL2BlockSource, - Algorithm, +use fuel_core_gas_price_service::v0::uninitialized_task::{ + new_gas_price_service_v0, AlgorithmV0, - FuelGasPriceUpdater, - UpdaterMetadata, - V0Metadata, }; use fuel_core_poa::{ signer::SignMode, Trigger, }; -use fuel_core_services::{ - RunnableService, - ServiceRunner, -}; use fuel_core_storage::{ self, transactional::AtomicView, @@ -79,7 +69,7 @@ pub type TxPoolSharedState = fuel_core_txpool::service::SharedState< P2PAdapter, Database, ExecutorAdapter, - FuelGasPriceProvider, + FuelGasPriceProvider, ConsensusParametersProvider, SharedMemoryPool, >; @@ -87,7 +77,7 @@ pub type BlockProducerService = fuel_core_producer::block_producer::Producer< Database, TxPoolAdapter, ExecutorAdapter, - FuelGasPriceProvider, + FuelGasPriceProvider, ConsensusParametersProvider, >; @@ -199,7 +189,7 @@ pub fn init_sub_services( let settings = consensus_parameters_provider.clone(); let block_stream = importer_adapter.events_shared_result(); - let gas_price_init = algorithm_updater::InitializeTask::new( + let gas_price_service_v0 = new_gas_price_service_v0( config.clone().into(), genesis_block_height, settings, @@ -207,10 +197,9 @@ pub fn init_sub_services( database.gas_price().clone(), database.on_chain().clone(), )?; - let next_algo = gas_price_init.shared_data(); - let gas_price_service = ServiceRunner::new(gas_price_init); - let gas_price_provider = FuelGasPriceProvider::new(next_algo); + let gas_price_provider = + FuelGasPriceProvider::new(gas_price_service_v0.shared.clone()); let txpool = fuel_core_txpool::new_service( config.txpool.clone(), database.on_chain().clone(), @@ -345,7 +334,7 @@ pub fn init_sub_services( #[allow(unused_mut)] // `FuelService` starts and shutdowns all sub-services in the `services` order let mut services: SubServices = vec![ - Box::new(gas_price_service), + Box::new(gas_price_service_v0), Box::new(txpool), Box::new(consensus_parameters_provider_service), ]; diff --git a/crates/services/gas_price_service/src/common.rs b/crates/services/gas_price_service/src/common.rs new file mode 100644 index 00000000000..4336abe6bf0 --- /dev/null +++ b/crates/services/gas_price_service/src/common.rs @@ -0,0 +1,5 @@ +pub mod fuel_core_storage_adapter; +pub mod gas_price_algorithm; +pub mod l2_block_source; +pub mod updater_metadata; +pub mod utils; diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter.rs b/crates/services/gas_price_service/src/common/fuel_core_storage_adapter.rs similarity index 50% rename from crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter.rs rename to crates/services/gas_price_service/src/common/fuel_core_storage_adapter.rs index 93fb0f7551c..4e76ee88e2f 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter.rs +++ b/crates/services/gas_price_service/src/common/fuel_core_storage_adapter.rs @@ -1,20 +1,18 @@ -use crate::fuel_gas_price_updater::{ +use crate::common::utils::{ BlockInfo, Error as GasPriceError, - Error, - L2BlockSource, - Result, Result as GasPriceResult, - UpdaterMetadata, }; use anyhow::anyhow; -use fuel_core_services::stream::BoxStream; use fuel_core_types::fuel_types::BlockHeight; use crate::{ - fuel_gas_price_updater::fuel_core_storage_adapter::storage::{ - GasPriceColumn, - GasPriceMetadata, + common::{ + fuel_core_storage_adapter::storage::{ + GasPriceColumn, + GasPriceMetadata, + }, + updater_metadata::UpdaterMetadata, }, ports::MetadataStorage, }; @@ -40,17 +38,12 @@ use fuel_core_types::{ }, Transaction, }, - services::block_importer::SharedImportResult, }; use std::cmp::min; -use tokio_stream::StreamExt; #[cfg(test)] mod metadata_tests; -#[cfg(test)] -mod l2_source_tests; - pub mod storage; impl MetadataStorage for StructuredStorage @@ -61,53 +54,30 @@ where fn get_metadata( &self, block_height: &BlockHeight, - ) -> Result> { + ) -> GasPriceResult> { let metadata = self .storage::() .get(block_height) - .map_err(|err| Error::CouldNotFetchMetadata { + .map_err(|err| GasPriceError::CouldNotFetchMetadata { source_error: err.into(), })?; Ok(metadata.map(|inner| inner.into_owned())) } - fn set_metadata(&mut self, metadata: &UpdaterMetadata) -> Result<()> { + fn set_metadata(&mut self, metadata: &UpdaterMetadata) -> GasPriceResult<()> { let block_height = metadata.l2_block_height(); let mut tx = self.write_transaction(); tx.storage_as_mut::() .insert(&block_height, metadata) - .map_err(|err| Error::CouldNotSetMetadata { + .and_then(|_| tx.commit()) + .map_err(|err| GasPriceError::CouldNotSetMetadata { block_height, source_error: err.into(), })?; - tx.commit().map_err(|err| Error::CouldNotSetMetadata { - block_height, - source_error: err.into(), - })?; Ok(()) } } -pub struct FuelL2BlockSource { - genesis_block_height: BlockHeight, - gas_price_settings: Settings, - committed_block_stream: BoxStream, -} - -impl FuelL2BlockSource { - pub fn new( - genesis_block_height: BlockHeight, - gas_price_settings: Settings, - committed_block_stream: BoxStream, - ) -> Self { - Self { - genesis_block_height, - gas_price_settings, - committed_block_stream, - } - } -} - #[derive(Debug, Clone, PartialEq)] pub struct GasPriceSettings { pub gas_price_factor: u64, @@ -117,7 +87,7 @@ pub trait GasPriceSettingsProvider: Send + Sync + Clone { fn settings( &self, param_version: &ConsensusParametersVersion, - ) -> Result; + ) -> GasPriceResult; } pub fn get_block_info( @@ -126,9 +96,7 @@ pub fn get_block_info( block_gas_limit: u64, ) -> GasPriceResult { let (fee, gas_price) = mint_values(block)?; - let height = *block.header().height(); - let used_gas = - block_used_gas(height, fee, gas_price, gas_price_factor, block_gas_limit)?; + let used_gas = block_used_gas(fee, gas_price, gas_price_factor, block_gas_limit)?; let info = BlockInfo::Block { height: (*block.header().height()).into(), gas_used: used_gas, @@ -143,13 +111,11 @@ fn mint_values(block: &Block) -> GasPriceResult<(u64, u64)> { .last() .and_then(|tx| tx.as_mint()) .ok_or(GasPriceError::CouldNotFetchL2Block { - block_height: *block.header().height(), source_error: anyhow!("Block has no mint transaction"), })?; Ok((*mint.mint_amount(), *mint.gas_price())) } fn block_used_gas( - block_height: BlockHeight, fee: u64, gas_price: u64, gas_price_factor: u64, @@ -158,7 +124,6 @@ fn block_used_gas( let scaled_fee = fee.checked_mul(gas_price_factor) .ok_or(GasPriceError::CouldNotFetchL2Block { - block_height, source_error: anyhow!( "Failed to scale fee by gas price factor, overflow" ), @@ -168,41 +133,3 @@ fn block_used_gas( let used_gas = min(approximate, max_used_gas); Ok(used_gas) } - -#[async_trait::async_trait] -impl L2BlockSource for FuelL2BlockSource -where - Settings: GasPriceSettingsProvider + Send + Sync, -{ - async fn get_l2_block(&mut self, height: BlockHeight) -> GasPriceResult { - let block = &self - .committed_block_stream - .next() - .await - .ok_or({ - GasPriceError::CouldNotFetchL2Block { - block_height: height, - source_error: anyhow!("No committed block found"), - } - })? - .sealed_block - .entity; - - match block.header().height().cmp(&self.genesis_block_height) { - std::cmp::Ordering::Less => Err(GasPriceError::CouldNotFetchL2Block { - block_height: height, - source_error: anyhow!("Block precedes expected genesis block height"), - }), - std::cmp::Ordering::Equal => Ok(BlockInfo::GenesisBlock), - std::cmp::Ordering::Greater => { - let param_version = block.header().consensus_parameters_version; - - let GasPriceSettings { - gas_price_factor, - block_gas_limit, - } = self.gas_price_settings.settings(¶m_version)?; - get_block_info(block, gas_price_factor, block_gas_limit) - } - } - } -} diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/metadata_tests.rs b/crates/services/gas_price_service/src/common/fuel_core_storage_adapter/metadata_tests.rs similarity index 92% rename from crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/metadata_tests.rs rename to crates/services/gas_price_service/src/common/fuel_core_storage_adapter/metadata_tests.rs index 070f7087d91..86a3c458f5e 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/metadata_tests.rs +++ b/crates/services/gas_price_service/src/common/fuel_core_storage_adapter/metadata_tests.rs @@ -1,11 +1,6 @@ #![allow(non_snake_case)] use super::*; -use crate::fuel_gas_price_updater::{ - fuel_core_storage_adapter::storage::GasPriceColumn, - AlgorithmUpdater, - UpdaterMetadata, -}; use fuel_core_storage::{ structured_storage::test::InMemoryStorage, transactional::{ @@ -28,7 +23,7 @@ fn arb_metadata_with_l2_height(l2_height: BlockHeight) -> UpdaterMetadata { l2_block_height: l2_height.into(), l2_block_fullness_threshold_percent: 0, }; - AlgorithmUpdater::V0(inner).into() + inner.into() } fn database() -> StorageTransaction> { diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/storage.rs b/crates/services/gas_price_service/src/common/fuel_core_storage_adapter/storage.rs similarity index 96% rename from crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/storage.rs rename to crates/services/gas_price_service/src/common/fuel_core_storage_adapter/storage.rs index 19caf3e2cde..43d6835dcc1 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/storage.rs +++ b/crates/services/gas_price_service/src/common/fuel_core_storage_adapter/storage.rs @@ -1,4 +1,4 @@ -use crate::fuel_gas_price_updater::UpdaterMetadata; +use crate::common::updater_metadata::UpdaterMetadata; use fuel_core_storage::{ blueprint::plain::Plain, codec::{ @@ -10,6 +10,7 @@ use fuel_core_storage::{ Mappable, }; use fuel_core_types::fuel_types::BlockHeight; + #[repr(u32)] #[derive( Copy, diff --git a/crates/services/gas_price_service/src/common/gas_price_algorithm.rs b/crates/services/gas_price_service/src/common/gas_price_algorithm.rs new file mode 100644 index 00000000000..9373f62095e --- /dev/null +++ b/crates/services/gas_price_service/src/common/gas_price_algorithm.rs @@ -0,0 +1,44 @@ +use fuel_core_types::fuel_types::BlockHeight; +use std::sync::Arc; +use tokio::sync::RwLock; + +pub trait GasPriceAlgorithm { + fn next_gas_price(&self) -> u64; + fn worst_case_gas_price(&self, block_height: BlockHeight) -> u64; +} + +#[derive(Debug, Default)] +pub struct SharedGasPriceAlgo(Arc>); + +impl Clone for SharedGasPriceAlgo { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl SharedGasPriceAlgo +where + A: Send + Sync, +{ + pub fn new_with_algorithm(algorithm: A) -> Self { + Self(Arc::new(RwLock::new(algorithm))) + } + + pub async fn update(&mut self, new_algo: A) { + let mut write_lock = self.0.write().await; + *write_lock = new_algo; + } +} + +impl SharedGasPriceAlgo +where + A: GasPriceAlgorithm + Send + Sync, +{ + pub async fn next_gas_price(&self) -> u64 { + self.0.read().await.next_gas_price() + } + + pub async fn worst_case_gas_price(&self, block_height: BlockHeight) -> u64 { + self.0.read().await.worst_case_gas_price(block_height) + } +} diff --git a/crates/services/gas_price_service/src/common/l2_block_source.rs b/crates/services/gas_price_service/src/common/l2_block_source.rs new file mode 100644 index 00000000000..5896ed183f7 --- /dev/null +++ b/crates/services/gas_price_service/src/common/l2_block_source.rs @@ -0,0 +1,83 @@ +#[cfg(test)] +mod tests; + +use crate::common::{ + fuel_core_storage_adapter::{ + get_block_info, + GasPriceSettings, + GasPriceSettingsProvider, + }, + utils::{ + BlockInfo, + Error as GasPriceError, + Result as GasPriceResult, + }, +}; +use anyhow::anyhow; +use fuel_core_services::stream::BoxStream; +use fuel_core_types::{ + fuel_types::BlockHeight, + services::block_importer::SharedImportResult, +}; +use tokio_stream::StreamExt; + +#[async_trait::async_trait] +pub trait L2BlockSource: Send + Sync { + async fn get_l2_block(&mut self) -> GasPriceResult; +} + +pub struct FuelL2BlockSource { + genesis_block_height: BlockHeight, + gas_price_settings: Settings, + committed_block_stream: BoxStream, +} + +impl FuelL2BlockSource { + pub fn new( + genesis_block_height: BlockHeight, + gas_price_settings: Settings, + committed_block_stream: BoxStream, + ) -> Self { + Self { + genesis_block_height, + gas_price_settings, + committed_block_stream, + } + } +} + +#[async_trait::async_trait] +impl L2BlockSource for FuelL2BlockSource +where + Settings: GasPriceSettingsProvider + Send + Sync, +{ + async fn get_l2_block(&mut self) -> GasPriceResult { + let block = &self + .committed_block_stream + .next() + .await + .ok_or({ + GasPriceError::CouldNotFetchL2Block { + source_error: anyhow!("No committed block found"), + } + })? + .sealed_block + .entity; + + match block.header().height().cmp(&self.genesis_block_height) { + std::cmp::Ordering::Less => Err(GasPriceError::CouldNotFetchL2Block { + source_error: anyhow!("Block precedes expected genesis block height"), + }), + std::cmp::Ordering::Equal => Ok(BlockInfo::GenesisBlock), + std::cmp::Ordering::Greater => { + let param_version = block.header().consensus_parameters_version; + + let GasPriceSettings { + gas_price_factor, + block_gas_limit, + } = self.gas_price_settings.settings(¶m_version)?; + get_block_info(block, gas_price_factor, block_gas_limit) + } + } + } +} diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/l2_source_tests.rs b/crates/services/gas_price_service/src/common/l2_block_source/tests.rs similarity index 92% rename from crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/l2_source_tests.rs rename to crates/services/gas_price_service/src/common/l2_block_source/tests.rs index 8d25fcb0c59..d2b71c7c930 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter/l2_source_tests.rs +++ b/crates/services/gas_price_service/src/common/l2_block_source/tests.rs @@ -1,13 +1,21 @@ #![allow(non_snake_case)] use super::*; +use crate::common::utils::{ + Error as GasPriceError, + Result as GasPriceResult, +}; use fuel_core_services::stream::{ BoxStream, IntoBoxStream, }; use fuel_core_types::{ blockchain::{ - block::CompressedBlock, + block::{ + Block, + CompressedBlock, + }, + header::ConsensusParametersVersion, SealedBlock, }, fuel_tx::{ @@ -16,8 +24,13 @@ use fuel_core_types::{ FeeParameters, FeeParametersV1, }, + field::{ + MintAmount, + MintGasPrice, + }, ConsensusParameters, Mint, + Transaction, UniqueIdentifier, }, fuel_types::ChainId, @@ -53,7 +66,7 @@ impl GasPriceSettingsProvider for FakeSettings { fn settings( &self, _param_version: &ConsensusParametersVersion, - ) -> Result { + ) -> GasPriceResult { Ok(GasPriceSettings { gas_price_factor: self.gas_price_factor, block_gas_limit: self.block_gas_limit, @@ -103,7 +116,6 @@ async fn get_l2_block__gets_expected_value() { let params = params(); let height = 1u32.into(); let (block, _mint) = build_block(¶ms.chain_id(), height); - let block_height = 1u32.into(); let genesis_block_height = 0u32.into(); let gas_price_factor = 100; let block_gas_limit = 1000; @@ -117,7 +129,7 @@ async fn get_l2_block__gets_expected_value() { let mut source = l2_source(genesis_block_height, settings, block_stream); // when - let actual = source.get_l2_block(block_height).await.unwrap(); + let actual = source.get_l2_block().await.unwrap(); // then assert_eq!(expected, actual); @@ -140,7 +152,7 @@ async fn get_l2_block__waits_for_block() { let mut source = l2_source(genesis_block_height, settings, block_stream); // when - let mut fut_l2_block = source.get_l2_block(block_height); + let mut fut_l2_block = source.get_l2_block(); for _ in 0..10 { fut_l2_block = match maybe_done(fut_l2_block) { MaybeDone::Future(fut) => { @@ -195,7 +207,7 @@ async fn get_l2_block__calculates_gas_used_correctly() { let mut source = l2_source(genesis_block_height, settings, block_stream); // when - let result = source.get_l2_block(block_height).await.unwrap(); + let result = source.get_l2_block().await.unwrap(); // then let BlockInfo::Block { @@ -236,7 +248,7 @@ async fn get_l2_block__calculates_block_gas_capacity_correctly() { let mut source = l2_source(genesis_block_height, settings, block_stream); // when - let result = source.get_l2_block(block_height).await.unwrap(); + let result = source.get_l2_block().await.unwrap(); // then let BlockInfo::Block { @@ -270,7 +282,7 @@ async fn get_l2_block__if_block_precedes_genesis_block_throw_an_error() { let mut source = l2_source(genesis_block_height, settings, block_stream); // when - let error = source.get_l2_block(block_height).await.unwrap_err(); + let error = source.get_l2_block().await.unwrap_err(); // then assert!(matches!(error, GasPriceError::CouldNotFetchL2Block { .. })); diff --git a/crates/services/gas_price_service/src/common/updater_metadata.rs b/crates/services/gas_price_service/src/common/updater_metadata.rs new file mode 100644 index 00000000000..b12fa32cf88 --- /dev/null +++ b/crates/services/gas_price_service/src/common/updater_metadata.rs @@ -0,0 +1,22 @@ +use crate::v0::metadata::V0Metadata; +use fuel_core_types::fuel_types::BlockHeight; +use fuel_gas_price_algorithm::v0::AlgorithmUpdaterV0; + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] +pub enum UpdaterMetadata { + V0(V0Metadata), +} + +impl UpdaterMetadata { + pub fn l2_block_height(&self) -> BlockHeight { + match self { + UpdaterMetadata::V0(v1) => v1.l2_block_height.into(), + } + } +} + +impl From for UpdaterMetadata { + fn from(updater: AlgorithmUpdaterV0) -> Self { + Self::V0(updater.into()) + } +} diff --git a/crates/services/gas_price_service/src/common/utils.rs b/crates/services/gas_price_service/src/common/utils.rs new file mode 100644 index 00000000000..2ee96cecec9 --- /dev/null +++ b/crates/services/gas_price_service/src/common/utils.rs @@ -0,0 +1,38 @@ +use fuel_core_types::fuel_types::BlockHeight; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Failed to find L2 block: {source_error:?}")] + CouldNotFetchL2Block { source_error: anyhow::Error }, + #[error("Failed to find DA records: {0:?}")] + CouldNotFetchDARecord(anyhow::Error), + #[error("Failed to retrieve updater metadata: {source_error:?}")] + CouldNotFetchMetadata { source_error: anyhow::Error }, + #[error( + "Failed to set updater metadata at height {block_height:?}: {source_error:?}" + )] + CouldNotSetMetadata { + block_height: BlockHeight, + source_error: anyhow::Error, + }, + #[error("Failed to initialize updater: {0:?}")] + CouldNotInitUpdater(anyhow::Error), +} + +pub type Result = core::result::Result; + +// Info required about the l2 block for the gas price algorithm +#[derive(Debug, Clone, PartialEq)] +pub enum BlockInfo { + // The genesis block of the L2 chain + GenesisBlock, + // A normal block in the L2 chain + Block { + // Block height + height: u32, + // Gas used in the block + gas_used: u64, + // Total gas capacity of the block + block_gas_capacity: u64, + }, +} diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater.rs b/crates/services/gas_price_service/src/fuel_gas_price_updater.rs deleted file mode 100644 index b5fde86f2e5..00000000000 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater.rs +++ /dev/null @@ -1,354 +0,0 @@ -use crate::{ - ports::MetadataStorage, - GasPriceAlgorithm, - UpdateAlgorithm, -}; -use anyhow::anyhow; -use core::num::NonZeroU64; -use fuel_core_types::fuel_types::BlockHeight; -pub use fuel_gas_price_algorithm::{ - v0::{ - AlgorithmUpdaterV0, - AlgorithmV0, - }, - v1::{ - AlgorithmUpdaterV1, - AlgorithmV1, - RecordedBlock, - }, -}; - -#[cfg(test)] -mod tests; - -pub mod fuel_core_storage_adapter; - -pub mod algorithm_updater; -pub mod da_source_adapter; - -pub struct FuelGasPriceUpdater { - inner: AlgorithmUpdater, - l2_block_source: L2, - metadata_storage: Metadata, - #[allow(dead_code)] - da_block_costs: DaBlockCosts, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum AlgorithmUpdater { - V0(AlgorithmUpdaterV0), - V1(AlgorithmUpdaterV1), -} - -impl AlgorithmUpdater { - pub fn algorithm(&self) -> Algorithm { - match self { - AlgorithmUpdater::V0(v0) => Algorithm::V0(v0.algorithm()), - AlgorithmUpdater::V1(v1) => Algorithm::V1(v1.algorithm()), - } - } - - pub fn l2_block_height(&self) -> BlockHeight { - match self { - AlgorithmUpdater::V0(v0) => v0.l2_block_height.into(), - AlgorithmUpdater::V1(v1) => v1.l2_block_height.into(), - } - } -} - -impl FuelGasPriceUpdater { - pub fn new( - inner: AlgorithmUpdater, - l2_block_source: L2, - metadata_storage: Metadata, - da_block_costs: DaBlockCosts, - ) -> Self { - Self { - inner, - l2_block_source, - metadata_storage, - da_block_costs, - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Failed to find L2 block at height {block_height:?}: {source_error:?}")] - CouldNotFetchL2Block { - block_height: BlockHeight, - source_error: anyhow::Error, - }, - #[error("Failed to find DA records: {0:?}")] - CouldNotFetchDARecord(anyhow::Error), - #[error("Failed to retrieve updater metadata: {source_error:?}")] - CouldNotFetchMetadata { source_error: anyhow::Error }, - #[error( - "Failed to set updater metadata at height {block_height:?}: {source_error:?}" - )] - CouldNotSetMetadata { - block_height: BlockHeight, - source_error: anyhow::Error, - }, - #[error("Failed to initialize updater: {0:?}")] - CouldNotInitUpdater(anyhow::Error), -} - -pub type Result = core::result::Result; - -// Info required about the l2 block for the gas price algorithm -#[derive(Debug, Clone, PartialEq)] -pub enum BlockInfo { - // The genesis block of the L2 chain - GenesisBlock, - // A normal block in the L2 chain - Block { - // Block height - height: u32, - // Gas used in the block - gas_used: u64, - // Total gas capacity of the block - block_gas_capacity: u64, - }, -} -#[async_trait::async_trait] -pub trait L2BlockSource: Send + Sync { - async fn get_l2_block(&mut self, height: BlockHeight) -> Result; -} - -#[derive(Debug, Default, Clone, Eq, Hash, PartialEq)] -pub struct DaBlockCosts { - pub l2_block_range: core::ops::Range, - pub blob_size_bytes: u32, - pub blob_cost_wei: u128, -} - -pub trait GetDaBlockCosts: Send + Sync { - fn get(&self) -> Result>; -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub enum UpdaterMetadata { - V0(V0Metadata), -} - -impl UpdaterMetadata { - pub fn l2_block_height(&self) -> BlockHeight { - match self { - UpdaterMetadata::V0(v1) => v1.l2_block_height.into(), - } - } -} - -impl From for AlgorithmUpdater { - fn from(metadata: UpdaterMetadata) -> Self { - match metadata { - UpdaterMetadata::V0(v1_no_da) => { - let V0Metadata { - new_exec_price, - min_exec_gas_price, - exec_gas_price_change_percent, - l2_block_height, - l2_block_fullness_threshold_percent, - } = v1_no_da; - let updater = AlgorithmUpdaterV0 { - new_exec_price, - min_exec_gas_price, - exec_gas_price_change_percent, - l2_block_height, - l2_block_fullness_threshold_percent, - }; - AlgorithmUpdater::V0(updater) - } - } - } -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub struct V0Metadata { - /// The gas price to cover the execution of the next block - pub new_exec_price: u64, - // Execution - /// The lowest the algorithm allows the exec gas price to go - pub min_exec_gas_price: u64, - /// The Percentage the execution gas price will change in a single block, either increase or decrease - /// based on the fullness of the last L2 block - pub exec_gas_price_change_percent: u64, - /// The height for which the `new_exec_price` is calculated, which should be the _next_ block - pub l2_block_height: u32, - /// The threshold of gas usage above and below which the gas price will increase or decrease - /// This is a percentage of the total capacity of the L2 block - pub l2_block_fullness_threshold_percent: u64, -} - -impl From for UpdaterMetadata { - fn from(updater: AlgorithmUpdater) -> Self { - match updater { - AlgorithmUpdater::V0(v0) => { - let metadata = V0Metadata { - new_exec_price: v0.new_exec_price, - min_exec_gas_price: v0.min_exec_gas_price, - exec_gas_price_change_percent: v0.exec_gas_price_change_percent, - l2_block_height: v0.l2_block_height, - l2_block_fullness_threshold_percent: v0 - .l2_block_fullness_threshold_percent, - }; - UpdaterMetadata::V0(metadata) - } - AlgorithmUpdater::V1(_v1) => { - unimplemented!() // https://github.com/FuelLabs/fuel-core/issues/2140 - } - } - } -} - -impl FuelGasPriceUpdater -where - Metadata: MetadataStorage, - DaBlockCosts: GetDaBlockCosts, -{ - pub fn init( - target_block_height: BlockHeight, - l2_block_source: L2, - metadata_storage: Metadata, - da_block_costs: DaBlockCosts, - min_exec_gas_price: u64, - exec_gas_price_change_percent: u64, - l2_block_fullness_threshold_percent: u64, - ) -> Result { - let old_metadata = metadata_storage - .get_metadata(&target_block_height) - .map_err(|err| Error::CouldNotInitUpdater(anyhow::anyhow!(err)))? - .ok_or(Error::CouldNotInitUpdater(anyhow::anyhow!( - "No metadata found for block height: {:?}", - target_block_height - )))?; - let inner = match old_metadata { - UpdaterMetadata::V0(old) => { - let v0 = AlgorithmUpdaterV0::new( - old.new_exec_price, - min_exec_gas_price, - exec_gas_price_change_percent, - old.l2_block_height, - l2_block_fullness_threshold_percent, - ); - AlgorithmUpdater::V0(v0) - } - }; - let updater = Self { - inner, - l2_block_source, - metadata_storage, - da_block_costs, - }; - Ok(updater) - } - - fn validate_block_gas_capacity( - &self, - block_gas_capacity: u64, - ) -> anyhow::Result { - NonZeroU64::new(block_gas_capacity) - .ok_or_else(|| anyhow!("Block gas capacity must be non-zero")) - } - - async fn set_metadata(&mut self) -> anyhow::Result<()> { - let metadata = self.inner.clone().into(); - self.metadata_storage - .set_metadata(&metadata) - .map_err(|err| anyhow!(err)) - } - - async fn handle_normal_block( - &mut self, - height: u32, - gas_used: u64, - block_gas_capacity: u64, - ) -> anyhow::Result<()> { - let capacity = self.validate_block_gas_capacity(block_gas_capacity)?; - - match &mut self.inner { - AlgorithmUpdater::V0(updater) => { - updater.update_l2_block_data(height, gas_used, capacity)?; - } - AlgorithmUpdater::V1(_) => { - return Err(anyhow!("V1 of the gas price algo has not been enabled yet")) - // TODO(#2139): update the DA record data with data received from the source - // updater.update_da_record_data(vec![])?; - } - } - - self.set_metadata().await?; - Ok(()) - } - - async fn apply_block_info_to_gas_algorithm( - &mut self, - l2_block: BlockInfo, - ) -> anyhow::Result<()> { - match l2_block { - BlockInfo::GenesisBlock => { - self.set_metadata().await?; - } - BlockInfo::Block { - height, - gas_used, - block_gas_capacity, - } => { - self.handle_normal_block(height, gas_used, block_gas_capacity) - .await?; - } - } - Ok(()) - } -} - -#[async_trait::async_trait] -impl UpdateAlgorithm - for FuelGasPriceUpdater -where - L2: L2BlockSource, - Metadata: MetadataStorage, - DaBlockCosts: GetDaBlockCosts, -{ - type Algorithm = Algorithm; - - fn start(&self, _for_block: BlockHeight) -> Self::Algorithm { - self.inner.algorithm() - } - - async fn next(&mut self) -> anyhow::Result { - let l2_block_res = self - .l2_block_source - .get_l2_block(self.inner.l2_block_height()) - .await; - tracing::info!("Received L2 block result: {:?}", l2_block_res); - let l2_block = l2_block_res?; - - self.apply_block_info_to_gas_algorithm(l2_block).await?; - - Ok(self.inner.algorithm()) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Algorithm { - V0(AlgorithmV0), - V1(AlgorithmV1), -} - -impl GasPriceAlgorithm for Algorithm { - fn next_gas_price(&self) -> u64 { - match self { - Algorithm::V0(v0) => v0.calculate(), - Algorithm::V1(v1) => v1.calculate(), - } - } - - fn worst_case_gas_price(&self, height: BlockHeight) -> u64 { - match self { - Algorithm::V0(v0) => v0.worst_case(height.into()), - Algorithm::V1(v1) => v1.worst_case(height.into()), - } - } -} diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/algorithm_updater.rs b/crates/services/gas_price_service/src/fuel_gas_price_updater/algorithm_updater.rs deleted file mode 100644 index b1e2c956416..00000000000 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/algorithm_updater.rs +++ /dev/null @@ -1,350 +0,0 @@ -use crate::{ - fuel_gas_price_updater::{ - da_source_adapter::{ - dummy_costs::DummyDaBlockCosts, - DaBlockCostsProvider, - DaBlockCostsSharedState, - }, - fuel_core_storage_adapter::{ - get_block_info, - storage::GasPriceColumn, - FuelL2BlockSource, - GasPriceSettings, - GasPriceSettingsProvider, - }, - Algorithm, - AlgorithmUpdater, - BlockInfo, - FuelGasPriceUpdater, - UpdaterMetadata, - V0Metadata, - }, - ports::{ - GasPriceData, - GasPriceServiceConfig, - L2Data, - MetadataStorage, - }, - GasPriceService, - SharedGasPriceAlgo, -}; -use fuel_core_services::{ - stream::BoxStream, - RunnableService, - Service, - StateWatcher, -}; -use fuel_core_storage::{ - kv_store::KeyValueInspect, - not_found, - structured_storage::StructuredStorage, - transactional::{ - AtomicView, - Modifiable, - }, -}; -use fuel_core_types::{ - fuel_types::BlockHeight, - services::block_importer::SharedImportResult, -}; -use fuel_gas_price_algorithm::v0::AlgorithmUpdaterV0; - -type Updater = FuelGasPriceUpdater< - FuelL2BlockSource, - StructuredStorage, - DaBlockCostsSharedState, ->; - -pub struct InitializeTask { - pub config: GasPriceServiceConfig, - pub genesis_block_height: BlockHeight, - pub settings: SettingsProvider, - pub gas_price_db: GasPriceStore, - pub on_chain_db: L2DataStoreView, - pub block_stream: BoxStream, - pub shared_algo: SharedGasPriceAlgo, - pub da_block_costs_provider: DaBlockCostsProvider, -} - -type Task = - GasPriceService>; - -impl - InitializeTask -where - L2DataStore: L2Data, - L2DataStoreView: AtomicView, - GasPriceStore: GasPriceData + Modifiable + KeyValueInspect, - SettingsProvider: GasPriceSettingsProvider, -{ - pub fn new( - config: GasPriceServiceConfig, - genesis_block_height: BlockHeight, - settings: SettingsProvider, - block_stream: BoxStream, - mut gas_price_db: GasPriceStore, - on_chain_db: L2DataStoreView, - ) -> anyhow::Result { - let view = on_chain_db.latest_view()?; - let latest_block_height = - view.latest_height().unwrap_or(genesis_block_height).into(); - let default_metadata = get_default_metadata(&config, latest_block_height); - let algo = get_best_algo(&mut gas_price_db, default_metadata)?; - let shared_algo = SharedGasPriceAlgo::new_with_algorithm(algo); - // there's no use of this source yet, so we can safely return an error - let da_block_costs_source = - DummyDaBlockCosts::new(Err(anyhow::anyhow!("Not used"))); - let da_block_costs_provider = - DaBlockCostsProvider::new(da_block_costs_source, None); - - let task = Self { - config, - genesis_block_height, - settings, - gas_price_db, - on_chain_db, - block_stream, - shared_algo, - da_block_costs_provider, - }; - Ok(task) - } -} - -fn get_default_metadata( - config: &GasPriceServiceConfig, - latest_block_height: u32, -) -> UpdaterMetadata { - UpdaterMetadata::V0(V0Metadata { - new_exec_price: config.starting_gas_price.max(config.min_gas_price), - min_exec_gas_price: config.min_gas_price, - exec_gas_price_change_percent: config.gas_price_change_percent, - l2_block_height: latest_block_height, - l2_block_fullness_threshold_percent: config.gas_price_threshold_percent, - }) -} - -fn get_best_algo( - gas_price_db: &mut GasPriceStore, - default_metadata: UpdaterMetadata, -) -> anyhow::Result -where - GasPriceStore: GasPriceData + Modifiable + KeyValueInspect, -{ - let best_metadata: UpdaterMetadata = - if let Some(height) = gas_price_db.latest_height() { - let metadata_storage = StructuredStorage::new(gas_price_db); - metadata_storage - .get_metadata(&height)? - .unwrap_or(default_metadata) - } else { - default_metadata - }; - let updater: AlgorithmUpdater = best_metadata.into(); - let algo = updater.algorithm(); - Ok(algo) -} -#[async_trait::async_trait] -impl RunnableService - for InitializeTask -where - L2DataStore: L2Data, - L2DataStoreView: AtomicView, - GasPriceStore: GasPriceData + Modifiable + KeyValueInspect, - SettingsProvider: GasPriceSettingsProvider, -{ - const NAME: &'static str = "GasPriceUpdater"; - type SharedData = SharedGasPriceAlgo; - type Task = Task; - type TaskParams = (); - - fn shared_data(&self) -> Self::SharedData { - self.shared_algo.clone() - } - - async fn into_task( - self, - _state_watcher: &StateWatcher, - _params: Self::TaskParams, - ) -> anyhow::Result { - let view = self.on_chain_db.latest_view()?; - let starting_height = view.latest_height().unwrap_or(self.genesis_block_height); - - let updater = get_synced_gas_price_updater( - self.config, - self.genesis_block_height, - self.settings, - self.gas_price_db, - &self.on_chain_db, - self.block_stream, - self.da_block_costs_provider.shared_state, - )?; - - self.da_block_costs_provider - .service - .start_and_await() - .await?; - let inner_service = - GasPriceService::new(starting_height, updater, self.shared_algo).await; - Ok(inner_service) - } -} - -pub fn get_synced_gas_price_updater< - L2DataStore, - L2DataStoreView, - GasPriceStore, - SettingsProvider, ->( - config: GasPriceServiceConfig, - genesis_block_height: BlockHeight, - settings: SettingsProvider, - gas_price_db: GasPriceStore, - on_chain_db: &L2DataStoreView, - block_stream: BoxStream, - da_block_costs: DaBlockCostsSharedState, -) -> anyhow::Result> -where - L2DataStore: L2Data, - L2DataStoreView: AtomicView, - GasPriceStore: GasPriceData + Modifiable + KeyValueInspect, - SettingsProvider: GasPriceSettingsProvider, -{ - let mut first_run = false; - let latest_block_height: u32 = on_chain_db - .latest_view()? - .latest_height() - .unwrap_or(genesis_block_height) - .into(); - - let maybe_metadata_height = gas_price_db.latest_height(); - let metadata_height = if let Some(metadata_height) = maybe_metadata_height { - metadata_height.into() - } else { - first_run = true; - latest_block_height - }; - let default_metadata = get_default_metadata(&config, latest_block_height); - - let l2_block_source = - FuelL2BlockSource::new(genesis_block_height, settings.clone(), block_stream); - - let mut metadata_storage = StructuredStorage::new(gas_price_db); - - if BlockHeight::from(latest_block_height) == genesis_block_height || first_run { - let updater = FuelGasPriceUpdater::new( - default_metadata.into(), - l2_block_source, - metadata_storage, - da_block_costs, - ); - Ok(updater) - } else { - if latest_block_height > metadata_height { - sync_gas_price_db_with_on_chain_storage( - &settings, - &mut metadata_storage, - on_chain_db, - metadata_height, - latest_block_height, - )?; - } - - FuelGasPriceUpdater::init( - latest_block_height.into(), - l2_block_source, - metadata_storage, - da_block_costs, - config.min_gas_price, - config.gas_price_change_percent, - config.gas_price_threshold_percent, - ) - .map_err(|e| anyhow::anyhow!("Could not initialize gas price updater: {e:?}")) - } -} - -fn sync_gas_price_db_with_on_chain_storage< - L2DataStore, - L2DataStoreView, - GasPriceStore, - SettingsProvider, ->( - settings: &SettingsProvider, - metadata_storage: &mut StructuredStorage, - on_chain_db: &L2DataStoreView, - metadata_height: u32, - latest_block_height: u32, -) -> anyhow::Result<()> -where - L2DataStore: L2Data, - L2DataStoreView: AtomicView, - GasPriceStore: GasPriceData + Modifiable + KeyValueInspect, - SettingsProvider: GasPriceSettingsProvider, -{ - let metadata = metadata_storage - .get_metadata(&metadata_height.into())? - .ok_or(anyhow::anyhow!( - "Expected metadata to exist for height: {metadata_height}" - ))?; - let mut inner: AlgorithmUpdater = metadata.into(); - match &mut inner { - AlgorithmUpdater::V0(ref mut updater) => { - sync_v0_metadata( - settings, - on_chain_db, - metadata_height, - latest_block_height, - updater, - metadata_storage, - )?; - } - AlgorithmUpdater::V1(_) => { - todo!() // TODO(#2140) - } - } - Ok(()) -} - -fn sync_v0_metadata( - settings: &SettingsProvider, - on_chain_db: &L2DataStoreView, - metadata_height: u32, - latest_block_height: u32, - updater: &mut AlgorithmUpdaterV0, - metadata_storage: &mut StructuredStorage, -) -> anyhow::Result<()> -where - L2DataStore: L2Data, - L2DataStoreView: AtomicView, - GasPriceStore: GasPriceData + Modifiable + KeyValueInspect, - SettingsProvider: GasPriceSettingsProvider, -{ - let first = metadata_height.saturating_add(1); - let view = on_chain_db.latest_view()?; - for height in first..=latest_block_height { - let block = view - .get_block(&height.into())? - .ok_or(not_found!("FullBlock"))?; - let param_version = block.header().consensus_parameters_version; - - let GasPriceSettings { - gas_price_factor, - block_gas_limit, - } = settings.settings(¶m_version)?; - let block_gas_capacity = block_gas_limit.try_into()?; - - let block_gas_used = - match get_block_info(&block, gas_price_factor, block_gas_limit)? { - BlockInfo::GenesisBlock => { - Err(anyhow::anyhow!("should not be genesis block"))? - } - BlockInfo::Block { gas_used, .. } => gas_used, - }; - - updater.update_l2_block_data(height, block_gas_used, block_gas_capacity)?; - let metadata = AlgorithmUpdater::V0(updater.clone()).into(); - metadata_storage.set_metadata(&metadata)?; - } - - Ok(()) -} diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/tests.rs b/crates/services/gas_price_service/src/fuel_gas_price_updater/tests.rs deleted file mode 100644 index 9c4ab7daff2..00000000000 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/tests.rs +++ /dev/null @@ -1,236 +0,0 @@ -#![allow(non_snake_case)] - -use super::*; -use std::sync::Arc; -use tokio::sync::mpsc::Receiver; - -struct FakeL2BlockSource { - l2_block: Receiver, -} - -#[async_trait::async_trait] -impl L2BlockSource for FakeL2BlockSource { - async fn get_l2_block(&mut self, _height: BlockHeight) -> Result { - let block = self.l2_block.recv().await.unwrap(); - Ok(block) - } -} - -struct PendingL2BlockSource; - -#[async_trait::async_trait] -impl L2BlockSource for PendingL2BlockSource { - async fn get_l2_block(&mut self, _height: BlockHeight) -> Result { - futures::future::pending().await - } -} - -struct FakeMetadata { - inner: Arc>>, -} - -impl FakeMetadata { - fn empty() -> Self { - Self { - inner: Arc::new(std::sync::Mutex::new(None)), - } - } -} - -impl MetadataStorage for FakeMetadata { - fn get_metadata(&self, _: &BlockHeight) -> Result> { - let metadata = self.inner.lock().unwrap().clone(); - Ok(metadata) - } - - fn set_metadata(&mut self, metadata: &UpdaterMetadata) -> Result<()> { - *self.inner.lock().unwrap() = Some(metadata.clone()); - Ok(()) - } -} - -fn arb_metadata() -> UpdaterMetadata { - UpdaterMetadata::V0(V0Metadata { - // set values - exec_gas_price_change_percent: 10, - new_exec_price: 100, - // unset values - l2_block_height: 0, - l2_block_fullness_threshold_percent: 0, - min_exec_gas_price: 0, - }) -} - -fn different_arb_metadata() -> UpdaterMetadata { - UpdaterMetadata::V0(V0Metadata { - // set values - exec_gas_price_change_percent: 20, - new_exec_price: 100, - // unset values - l2_block_height: 0, - l2_block_fullness_threshold_percent: 0, - min_exec_gas_price: 0, - }) -} - -#[derive(Default, Clone)] -struct FakeDaSource { - called: Arc>, -} - -impl FakeDaSource { - fn new() -> Self { - Self { - called: Arc::new(std::sync::Mutex::new(false)), - } - } - - fn was_called(&self) -> bool { - *self.called.lock().unwrap() - } -} - -impl GetDaBlockCosts for FakeDaSource { - fn get(&self) -> Result> { - *self.called.lock().unwrap() = true; - Ok(Some(DaBlockCosts::default())) - } -} - -#[tokio::test] -async fn next__fetches_l2_block() { - // given - let l2_block = BlockInfo::Block { - height: 1, - gas_used: 60, - block_gas_capacity: 100, - }; - let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); - let l2_block_source = FakeL2BlockSource { - l2_block: l2_block_receiver, - }; - let metadata_storage = FakeMetadata::empty(); - - let starting_metadata = arb_metadata(); - let fake_da_source = FakeDaSource::new(); - let mut updater = FuelGasPriceUpdater::new( - starting_metadata.into(), - l2_block_source, - metadata_storage, - fake_da_source.clone(), - ); - - let start = updater.start(0.into()); - // when - let next = tokio::spawn(async move { updater.next().await }); - - l2_block_sender.send(l2_block).await.unwrap(); - let new = next.await.unwrap().unwrap(); - - // then - assert_ne!(start, new); - match start { - Algorithm::V0(_) => {} - Algorithm::V1(_) => { - assert!(fake_da_source.was_called()); - } - } -} - -#[tokio::test] -async fn next__new_l2_block_saves_old_metadata() { - // given - let l2_block = BlockInfo::Block { - height: 1, - gas_used: 60, - block_gas_capacity: 100, - }; - let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); - let l2_block_source = FakeL2BlockSource { - l2_block: l2_block_receiver, - }; - let metadata_inner = Arc::new(std::sync::Mutex::new(None)); - let metadata_storage = FakeMetadata { - inner: metadata_inner.clone(), - }; - - let starting_metadata = arb_metadata(); - let mut updater = FuelGasPriceUpdater::new( - starting_metadata.into(), - l2_block_source, - metadata_storage, - FakeDaSource::default(), - ); - - let start = updater.start(0.into()); - // when - let next = tokio::spawn(async move { updater.next().await }); - - l2_block_sender.send(l2_block).await.unwrap(); - let new = next.await.unwrap().unwrap(); - - // then - assert_ne!(start, new); -} - -#[tokio::test] -async fn init__if_exists_already_reload_old_values_with_overrides() { - // given - let original = arb_metadata(); - let metadata_inner = Arc::new(std::sync::Mutex::new(Some(original.clone()))); - let metadata_storage = FakeMetadata { - inner: metadata_inner, - }; - let l2_block_source = PendingL2BlockSource; - let new_min_exec_gas_price = 99; - let new_exec_gas_price_change_percent = 88; - let new_l2_block_fullness_threshold_percent = 77; - - // when - let height = original.l2_block_height(); - let updater = FuelGasPriceUpdater::init( - height, - l2_block_source, - metadata_storage, - FakeDaSource::default(), - new_min_exec_gas_price, - new_exec_gas_price_change_percent, - new_l2_block_fullness_threshold_percent, - ) - .unwrap(); - - // then - let UpdaterMetadata::V0(original_inner) = original; - let expected: AlgorithmUpdater = UpdaterMetadata::V0(V0Metadata { - min_exec_gas_price: new_min_exec_gas_price, - exec_gas_price_change_percent: new_exec_gas_price_change_percent, - l2_block_fullness_threshold_percent: new_l2_block_fullness_threshold_percent, - ..original_inner - }) - .into(); - let actual = updater.inner; - assert_eq!(expected, actual); -} - -#[tokio::test] -async fn init__if_it_does_not_exist_fail() { - // given - let metadata_storage = FakeMetadata::empty(); - let l2_block_source = PendingL2BlockSource; - - // when - let metadata = different_arb_metadata(); - let height = u32::from(metadata.l2_block_height()) + 1; - let res = FuelGasPriceUpdater::init( - height.into(), - l2_block_source, - metadata_storage, - FakeDaSource::default(), - 0, - 0, - 0, - ); - - // then - assert!(matches!(res, Err(Error::CouldNotInitUpdater(_)))); -} diff --git a/crates/services/gas_price_service/src/lib.rs b/crates/services/gas_price_service/src/lib.rs index 2e254ed037e..c045bad4118 100644 --- a/crates/services/gas_price_service/src/lib.rs +++ b/crates/services/gas_price_service/src/lib.rs @@ -3,251 +3,12 @@ #![deny(unused_crate_dependencies)] #![deny(warnings)] -use async_trait::async_trait; -use fuel_core_services::{ - RunnableService, - RunnableTask, - StateWatcher, -}; -use fuel_core_types::fuel_types::BlockHeight; -use futures::FutureExt; -use std::sync::Arc; - -use tokio::sync::RwLock; - +#[allow(unused)] pub mod static_updater; -pub mod fuel_gas_price_updater; pub mod ports; +pub mod v0; -/// The service that updates the gas price algorithm. -pub struct GasPriceService { - /// The algorithm that can be used in the next block - next_block_algorithm: SharedGasPriceAlgo, - /// The code that is run to update your specific algorithm - update_algorithm: U, -} - -impl GasPriceService -where - U: UpdateAlgorithm, - A: Send + Sync, -{ - pub async fn new( - starting_block_height: BlockHeight, - update_algorithm: U, - mut shared_algo: SharedGasPriceAlgo, - ) -> Self { - let algorithm = update_algorithm.start(starting_block_height); - shared_algo.update(algorithm).await; - Self { - next_block_algorithm: shared_algo, - update_algorithm, - } - } - - pub fn next_block_algorithm(&self) -> SharedGasPriceAlgo { - self.next_block_algorithm.clone() - } -} - -/// The interface the Service has with the code that updates the gas price algorithm. -#[async_trait] -pub trait UpdateAlgorithm { - /// The type of the algorithm that is being updated - type Algorithm; - - /// Start the algorithm at a given block height - fn start(&self, for_block: BlockHeight) -> Self::Algorithm; - - /// Wait for the next algorithm to be available - async fn next(&mut self) -> anyhow::Result; -} - -pub trait GasPriceAlgorithm { - fn next_gas_price(&self) -> u64; - fn worst_case_gas_price(&self, block_height: BlockHeight) -> u64; -} - -impl GasPriceService -where - U: UpdateAlgorithm, - A: Send + Sync, -{ - async fn update(&mut self, new_algorithm: A) { - self.next_block_algorithm.update(new_algorithm).await; - } -} - -#[derive(Debug, Default)] -pub struct SharedGasPriceAlgo(Arc>); - -impl Clone for SharedGasPriceAlgo { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl SharedGasPriceAlgo -where - A: Send + Sync, -{ - pub fn new_with_algorithm(algorithm: A) -> Self { - Self(Arc::new(RwLock::new(algorithm))) - } - - pub async fn update(&mut self, new_algo: A) { - let mut write_lock = self.0.write().await; - *write_lock = new_algo; - } -} - -impl SharedGasPriceAlgo -where - A: GasPriceAlgorithm + Send + Sync, -{ - pub async fn next_gas_price(&self) -> u64 { - self.0.read().await.next_gas_price() - } - - pub async fn worst_case_gas_price(&self, block_height: BlockHeight) -> u64 { - self.0.read().await.worst_case_gas_price(block_height) - } -} - -#[async_trait] -impl RunnableService for GasPriceService -where - U: UpdateAlgorithm + Send + Sync, - A: Send + Sync, -{ - const NAME: &'static str = "GasPriceUpdater"; - type SharedData = SharedGasPriceAlgo; - type Task = Self; - type TaskParams = (); - - fn shared_data(&self) -> Self::SharedData { - self.next_block_algorithm.clone() - } - - async fn into_task( - self, - _state_watcher: &StateWatcher, - _params: Self::TaskParams, - ) -> anyhow::Result { - Ok(self) - } -} - -#[async_trait] -impl RunnableTask for GasPriceService -where - U: UpdateAlgorithm + Send + Sync, - A: Send + Sync, -{ - async fn run(&mut self, watcher: &mut StateWatcher) -> anyhow::Result { - let should_continue; - tokio::select! { - biased; - _ = watcher.while_started() => { - tracing::debug!("Stopping gas price service"); - should_continue = false; - } - new_algo = self.update_algorithm.next() => { - let new_algo = new_algo?; - tracing::debug!("Updating gas price algorithm"); - self.update(new_algo).await; - should_continue = true; - } - } - Ok(should_continue) - } - - async fn shutdown(mut self) -> anyhow::Result<()> { - while let Some(new_algo) = self.update_algorithm.next().now_or_never() { - let new_algo = new_algo?; - tracing::debug!("Updating gas price algorithm"); - self.update(new_algo).await; - } - Ok(()) - } -} - -#[allow(clippy::arithmetic_side_effects)] -#[allow(non_snake_case)] -#[cfg(test)] -mod tests { - use crate::{ - GasPriceAlgorithm, - GasPriceService, - SharedGasPriceAlgo, - UpdateAlgorithm, - }; - use fuel_core_services::{ - Service, - ServiceRunner, - }; - use fuel_core_types::fuel_types::BlockHeight; - use tokio::sync::mpsc; - - #[derive(Clone, Debug)] - struct TestAlgorithm { - price: u64, - } - - impl GasPriceAlgorithm for TestAlgorithm { - fn next_gas_price(&self) -> u64 { - self.price - } - - fn worst_case_gas_price(&self, _block_height: BlockHeight) -> u64 { - self.price - } - } - - struct TestAlgorithmUpdater { - start: TestAlgorithm, - price_source: mpsc::Receiver, - } - - #[async_trait::async_trait] - impl UpdateAlgorithm for TestAlgorithmUpdater { - type Algorithm = TestAlgorithm; - - fn start(&self, _for_block: BlockHeight) -> Self::Algorithm { - self.start.clone() - } - - async fn next(&mut self) -> anyhow::Result { - let price = self.price_source.recv().await.unwrap(); - Ok(TestAlgorithm { price }) - } - } - #[tokio::test] - async fn run__updates_gas_price() { - // given - let (price_sender, price_receiver) = mpsc::channel(1); - let start_algo = TestAlgorithm { price: 50 }; - let expected_price = 100; - let updater = TestAlgorithmUpdater { - start: TestAlgorithm { - price: expected_price, - }, - price_source: price_receiver, - }; - let shared_algo = SharedGasPriceAlgo::new_with_algorithm(start_algo); - let service = GasPriceService::new(0.into(), updater, shared_algo).await; - let read_algo = service.next_block_algorithm(); - let service = ServiceRunner::new(service); - service.start_and_await().await.unwrap(); - - // when - price_sender.send(expected_price).await.unwrap(); - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - - // then - let actual_price = read_algo.next_gas_price().await; - assert_eq!(expected_price, actual_price); - service.stop_and_await().await.unwrap(); - } -} +pub mod common; +#[allow(unused)] +pub mod v1; diff --git a/crates/services/gas_price_service/src/ports.rs b/crates/services/gas_price_service/src/ports.rs index f5d394d36fd..cefa85f4e01 100644 --- a/crates/services/gas_price_service/src/ports.rs +++ b/crates/services/gas_price_service/src/ports.rs @@ -1,6 +1,6 @@ -use crate::fuel_gas_price_updater::{ - Result, - UpdaterMetadata, +use crate::common::{ + updater_metadata::UpdaterMetadata, + utils::Result, }; use fuel_core_storage::Result as StorageResult; use fuel_core_types::{ diff --git a/crates/services/gas_price_service/src/static_updater.rs b/crates/services/gas_price_service/src/static_updater.rs index c2f45a3d60b..8185264c86c 100644 --- a/crates/services/gas_price_service/src/static_updater.rs +++ b/crates/services/gas_price_service/src/static_updater.rs @@ -1,8 +1,4 @@ -use crate::{ - GasPriceAlgorithm, - UpdateAlgorithm, -}; -use async_trait::async_trait; +use crate::common::gas_price_algorithm::GasPriceAlgorithm; use fuel_core_types::fuel_types::BlockHeight; pub struct StaticAlgorithmUpdater { @@ -39,15 +35,3 @@ impl GasPriceAlgorithm for StaticAlgorithm { self.price() } } -#[async_trait] -impl UpdateAlgorithm for StaticAlgorithmUpdater { - type Algorithm = StaticAlgorithm; - - fn start(&self, _for_block: BlockHeight) -> Self::Algorithm { - StaticAlgorithm::new(self.static_price) - } - - async fn next(&mut self) -> anyhow::Result { - futures::future::pending().await - } -} diff --git a/crates/services/gas_price_service/src/v0.rs b/crates/services/gas_price_service/src/v0.rs new file mode 100644 index 00000000000..e7ced08c1f1 --- /dev/null +++ b/crates/services/gas_price_service/src/v0.rs @@ -0,0 +1,6 @@ +pub mod algorithm; +pub mod metadata; +pub mod service; +#[cfg(test)] +mod tests; +pub mod uninitialized_task; diff --git a/crates/services/gas_price_service/src/v0/algorithm.rs b/crates/services/gas_price_service/src/v0/algorithm.rs new file mode 100644 index 00000000000..34209eb4f09 --- /dev/null +++ b/crates/services/gas_price_service/src/v0/algorithm.rs @@ -0,0 +1,13 @@ +use crate::common::gas_price_algorithm::GasPriceAlgorithm; +use fuel_core_types::fuel_types::BlockHeight; +use fuel_gas_price_algorithm::v0::AlgorithmV0; + +impl GasPriceAlgorithm for AlgorithmV0 { + fn next_gas_price(&self) -> u64 { + self.calculate() + } + + fn worst_case_gas_price(&self, block_height: BlockHeight) -> u64 { + self.worst_case(block_height.into()) + } +} diff --git a/crates/services/gas_price_service/src/v0/metadata.rs b/crates/services/gas_price_service/src/v0/metadata.rs new file mode 100644 index 00000000000..0d21b7b06e0 --- /dev/null +++ b/crates/services/gas_price_service/src/v0/metadata.rs @@ -0,0 +1,44 @@ +use fuel_gas_price_algorithm::v0::AlgorithmUpdaterV0; + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] +pub struct V0Metadata { + /// The gas price to cover the execution of the next block + pub new_exec_price: u64, + // Execution + /// The lowest the algorithm allows the exec gas price to go + pub min_exec_gas_price: u64, + /// The Percentage the execution gas price will change in a single block, either increase or decrease + /// based on the fullness of the last L2 block + pub exec_gas_price_change_percent: u64, + /// The height for which the `new_exec_price` is calculated, which should be the _next_ block + pub l2_block_height: u32, + /// The threshold of gas usage above and below which the gas price will increase or decrease + /// This is a percentage of the total capacity of the L2 block + pub l2_block_fullness_threshold_percent: u64, +} + +impl From for AlgorithmUpdaterV0 { + fn from(metadata: V0Metadata) -> Self { + Self { + new_exec_price: metadata.new_exec_price, + min_exec_gas_price: metadata.min_exec_gas_price, + exec_gas_price_change_percent: metadata.exec_gas_price_change_percent, + l2_block_height: metadata.l2_block_height, + l2_block_fullness_threshold_percent: metadata + .l2_block_fullness_threshold_percent, + } + } +} + +impl From for V0Metadata { + fn from(updater: AlgorithmUpdaterV0) -> Self { + Self { + new_exec_price: updater.new_exec_price, + min_exec_gas_price: updater.min_exec_gas_price, + exec_gas_price_change_percent: updater.exec_gas_price_change_percent, + l2_block_height: updater.l2_block_height, + l2_block_fullness_threshold_percent: updater + .l2_block_fullness_threshold_percent, + } + } +} diff --git a/crates/services/gas_price_service/src/v0/service.rs b/crates/services/gas_price_service/src/v0/service.rs new file mode 100644 index 00000000000..ed73b03063d --- /dev/null +++ b/crates/services/gas_price_service/src/v0/service.rs @@ -0,0 +1,291 @@ +use crate::{ + common::{ + l2_block_source::L2BlockSource, + updater_metadata::UpdaterMetadata, + utils::BlockInfo, + }, + ports::MetadataStorage, + v0::uninitialized_task::SharedV0Algorithm, +}; +use anyhow::anyhow; +use async_trait::async_trait; +use fuel_core_services::{ + RunnableService, + RunnableTask, + StateWatcher, +}; +use fuel_gas_price_algorithm::v0::{ + AlgorithmUpdaterV0, + AlgorithmV0, +}; +use futures::FutureExt; +use std::num::NonZeroU64; + +/// The service that updates the gas price algorithm. +pub struct GasPriceServiceV0 { + /// The algorithm that can be used in the next block + shared_algo: SharedV0Algorithm, + /// The L2 block source + l2_block_source: L2, + /// The metadata storage + metadata_storage: Metadata, + /// The algorithm updater + algorithm_updater: AlgorithmUpdaterV0, +} + +impl GasPriceServiceV0 +where + Metadata: MetadataStorage, +{ + pub fn new( + l2_block_source: L2, + metadata_storage: Metadata, + shared_algo: SharedV0Algorithm, + algorithm_updater: AlgorithmUpdaterV0, + ) -> Self { + Self { + shared_algo, + l2_block_source, + metadata_storage, + algorithm_updater, + } + } + + pub fn algorithm_updater(&self) -> &AlgorithmUpdaterV0 { + &self.algorithm_updater + } + + pub fn next_block_algorithm(&self) -> SharedV0Algorithm { + self.shared_algo.clone() + } + + async fn update(&mut self, new_algorithm: AlgorithmV0) { + self.shared_algo.update(new_algorithm).await; + } + + fn validate_block_gas_capacity( + &self, + block_gas_capacity: u64, + ) -> anyhow::Result { + NonZeroU64::new(block_gas_capacity) + .ok_or_else(|| anyhow!("Block gas capacity must be non-zero")) + } + + async fn set_metadata(&mut self) -> anyhow::Result<()> { + let metadata: UpdaterMetadata = self.algorithm_updater.clone().into(); + self.metadata_storage + .set_metadata(&metadata) + .map_err(|err| anyhow!(err)) + } + + async fn handle_normal_block( + &mut self, + height: u32, + gas_used: u64, + block_gas_capacity: u64, + ) -> anyhow::Result<()> { + let capacity = self.validate_block_gas_capacity(block_gas_capacity)?; + + self.algorithm_updater + .update_l2_block_data(height, gas_used, capacity)?; + + self.set_metadata().await?; + Ok(()) + } + + async fn apply_block_info_to_gas_algorithm( + &mut self, + l2_block: BlockInfo, + ) -> anyhow::Result<()> { + match l2_block { + BlockInfo::GenesisBlock => { + self.set_metadata().await?; + } + BlockInfo::Block { + height, + gas_used, + block_gas_capacity, + } => { + self.handle_normal_block(height, gas_used, block_gas_capacity) + .await?; + } + } + + self.update(self.algorithm_updater.algorithm()).await; + Ok(()) + } +} + +#[async_trait] +impl RunnableService for GasPriceServiceV0 +where + L2: L2BlockSource, + Metadata: MetadataStorage, +{ + const NAME: &'static str = "GasPriceServiceV0"; + type SharedData = SharedV0Algorithm; + type Task = Self; + type TaskParams = (); + + fn shared_data(&self) -> Self::SharedData { + self.shared_algo.clone() + } + + async fn into_task( + mut self, + _state_watcher: &StateWatcher, + _params: Self::TaskParams, + ) -> anyhow::Result { + let algorithm = self.algorithm_updater.algorithm(); + self.shared_algo.update(algorithm).await; + Ok(self) + } +} + +#[async_trait] +impl RunnableTask for GasPriceServiceV0 +where + L2: L2BlockSource, + Metadata: MetadataStorage, +{ + async fn run(&mut self, watcher: &mut StateWatcher) -> anyhow::Result { + let should_continue; + tokio::select! { + biased; + _ = watcher.while_started() => { + tracing::debug!("Stopping gas price service"); + should_continue = false; + } + l2_block_res = self.l2_block_source.get_l2_block() => { + tracing::info!("Received L2 block result: {:?}", l2_block_res); + let block = l2_block_res?; + + tracing::debug!("Updating gas price algorithm"); + self.apply_block_info_to_gas_algorithm(block).await?; + should_continue = true; + } + } + Ok(should_continue) + } + + async fn shutdown(mut self) -> anyhow::Result<()> { + while let Some(Ok(block)) = self.l2_block_source.get_l2_block().now_or_never() { + tracing::debug!("Updating gas price algorithm"); + self.apply_block_info_to_gas_algorithm(block).await?; + } + Ok(()) + } +} + +#[allow(clippy::arithmetic_side_effects)] +#[allow(non_snake_case)] +#[cfg(test)] +mod tests { + use crate::{ + common::{ + l2_block_source::L2BlockSource, + updater_metadata::UpdaterMetadata, + utils::{ + BlockInfo, + Result as GasPriceResult, + }, + }, + ports::MetadataStorage, + v0::{ + metadata::V0Metadata, + service::GasPriceServiceV0, + uninitialized_task::initialize_algorithm, + }, + }; + use fuel_core_services::{ + Service, + ServiceRunner, + }; + use fuel_core_types::fuel_types::BlockHeight; + use std::sync::Arc; + use tokio::sync::mpsc; + + struct FakeL2BlockSource { + l2_block: mpsc::Receiver, + } + + #[async_trait::async_trait] + impl L2BlockSource for FakeL2BlockSource { + async fn get_l2_block(&mut self) -> GasPriceResult { + let block = self.l2_block.recv().await.unwrap(); + Ok(block) + } + } + + struct FakeMetadata { + inner: Arc>>, + } + + impl FakeMetadata { + fn empty() -> Self { + Self { + inner: Arc::new(std::sync::Mutex::new(None)), + } + } + } + + impl MetadataStorage for FakeMetadata { + fn get_metadata( + &self, + _: &BlockHeight, + ) -> GasPriceResult> { + let metadata = self.inner.lock().unwrap().clone(); + Ok(metadata) + } + + fn set_metadata(&mut self, metadata: &UpdaterMetadata) -> GasPriceResult<()> { + *self.inner.lock().unwrap() = Some(metadata.clone()); + Ok(()) + } + } + + #[tokio::test] + async fn run__updates_gas_price() { + // given + let block_height = 1; + let l2_block = BlockInfo::Block { + height: block_height, + gas_used: 60, + block_gas_capacity: 100, + }; + let (l2_block_sender, l2_block_receiver) = mpsc::channel(1); + let l2_block_source = FakeL2BlockSource { + l2_block: l2_block_receiver, + }; + let metadata_storage = FakeMetadata::empty(); + let starting_metadata = V0Metadata { + min_exec_gas_price: 10, + exec_gas_price_change_percent: 10, + new_exec_price: 100, + l2_block_fullness_threshold_percent: 0, + l2_block_height: 0, + }; + let (algo_updater, shared_algo) = + initialize_algorithm(starting_metadata.clone(), &metadata_storage).unwrap(); + + let service = GasPriceServiceV0::new( + l2_block_source, + metadata_storage, + shared_algo, + algo_updater, + ); + let read_algo = service.next_block_algorithm(); + let service = ServiceRunner::new(service); + let prev = read_algo.next_gas_price().await; + + // when + service.start_and_await().await.unwrap(); + l2_block_sender.send(l2_block).await.unwrap(); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + + // then + let actual_price = read_algo.next_gas_price().await; + assert_ne!(prev, actual_price); + service.stop_and_await().await.unwrap(); + } +} diff --git a/crates/services/gas_price_service/src/v0/tests.rs b/crates/services/gas_price_service/src/v0/tests.rs new file mode 100644 index 00000000000..7385ead94bc --- /dev/null +++ b/crates/services/gas_price_service/src/v0/tests.rs @@ -0,0 +1,246 @@ +#![allow(non_snake_case)] + +use crate::{ + common::{ + l2_block_source::L2BlockSource, + updater_metadata::UpdaterMetadata, + utils::{ + BlockInfo, + Error as GasPriceError, + Result as GasPriceResult, + }, + }, + ports::MetadataStorage, + v0::{ + metadata::V0Metadata, + service::GasPriceServiceV0, + uninitialized_task::initialize_algorithm, + }, +}; +use anyhow::anyhow; +use fuel_core_services::{ + Service, + ServiceRunner, +}; +use fuel_core_types::fuel_types::BlockHeight; +use std::{ + sync::Arc, + time::Duration, +}; +use tokio::sync::mpsc::Receiver; + +struct FakeL2BlockSource { + l2_block: Receiver, +} + +#[async_trait::async_trait] +impl L2BlockSource for FakeL2BlockSource { + async fn get_l2_block(&mut self) -> GasPriceResult { + let block = self.l2_block.recv().await.unwrap(); + Ok(block) + } +} + +struct PendingL2BlockSource; + +#[async_trait::async_trait] +impl L2BlockSource for PendingL2BlockSource { + async fn get_l2_block(&mut self) -> GasPriceResult { + futures::future::pending().await + } +} + +struct FakeMetadata { + inner: Arc>>, +} + +impl FakeMetadata { + fn empty() -> Self { + Self { + inner: Arc::new(std::sync::Mutex::new(None)), + } + } +} + +impl MetadataStorage for FakeMetadata { + fn get_metadata(&self, _: &BlockHeight) -> GasPriceResult> { + let metadata = self.inner.lock().unwrap().clone(); + Ok(metadata) + } + + fn set_metadata(&mut self, metadata: &UpdaterMetadata) -> GasPriceResult<()> { + *self.inner.lock().unwrap() = Some(metadata.clone()); + Ok(()) + } +} + +struct ErroringMetadata; + +impl MetadataStorage for ErroringMetadata { + fn get_metadata(&self, _: &BlockHeight) -> GasPriceResult> { + Err(GasPriceError::CouldNotFetchMetadata { + source_error: anyhow!("boo!"), + }) + } + + fn set_metadata(&mut self, _: &UpdaterMetadata) -> GasPriceResult<()> { + Err(GasPriceError::CouldNotSetMetadata { + block_height: Default::default(), + source_error: anyhow!("boo!"), + }) + } +} + +fn arb_metadata() -> V0Metadata { + V0Metadata { + // set values + exec_gas_price_change_percent: 10, + new_exec_price: 100, + // unset values + l2_block_height: 0, + l2_block_fullness_threshold_percent: 0, + min_exec_gas_price: 0, + } +} + +fn different_arb_metadata() -> V0Metadata { + V0Metadata { + // set values + exec_gas_price_change_percent: 20, + new_exec_price: 100, + // unset values + l2_block_height: 0, + l2_block_fullness_threshold_percent: 0, + min_exec_gas_price: 0, + } +} + +#[tokio::test] +async fn next_gas_price__affected_by_new_l2_block() { + // given + let l2_block = BlockInfo::Block { + height: 1, + gas_used: 60, + block_gas_capacity: 100, + }; + let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); + let l2_block_source = FakeL2BlockSource { + l2_block: l2_block_receiver, + }; + let metadata_storage = FakeMetadata::empty(); + + let starting_metadata = arb_metadata(); + let (algo_updater, shared_algo) = + initialize_algorithm(starting_metadata.clone(), &metadata_storage).unwrap(); + let service = GasPriceServiceV0::new( + l2_block_source, + metadata_storage, + shared_algo, + algo_updater, + ); + let service = ServiceRunner::new(service); + let shared = service.shared.clone(); + let initial = shared.next_gas_price().await; + + // when + service.start_and_await().await.unwrap(); + l2_block_sender.send(l2_block).await.unwrap(); + tokio::time::sleep(Duration::from_millis(10)).await; + + // then + let new = shared.next_gas_price().await; + assert_ne!(initial, new); + service.stop_and_await().await.unwrap(); +} + +#[tokio::test] +async fn next__new_l2_block_saves_old_metadata() { + // given + let l2_block = BlockInfo::Block { + height: 1, + gas_used: 60, + block_gas_capacity: 100, + }; + let (l2_block_sender, l2_block_receiver) = tokio::sync::mpsc::channel(1); + let l2_block_source = FakeL2BlockSource { + l2_block: l2_block_receiver, + }; + let metadata_inner = Arc::new(std::sync::Mutex::new(None)); + let metadata_storage = FakeMetadata { + inner: metadata_inner.clone(), + }; + + let starting_metadata = arb_metadata(); + let (algo_updater, shared_algo) = + initialize_algorithm(starting_metadata.clone(), &metadata_storage).unwrap(); + + let service = GasPriceServiceV0::new( + l2_block_source, + metadata_storage, + shared_algo, + algo_updater, + ); + + // when + let service = ServiceRunner::new(service); + let shared = service.shared.clone(); + let start = shared.next_gas_price().await; + + service.start_and_await().await.unwrap(); + l2_block_sender.send(l2_block).await.unwrap(); + tokio::time::sleep(Duration::from_millis(10)).await; + + // then + let new = shared.next_gas_price().await; + assert_ne!(start, new); +} + +#[tokio::test] +async fn new__if_exists_already_reload_old_values_with_overrides() { + // given + let original = UpdaterMetadata::V0(arb_metadata()); + let metadata_inner = Arc::new(std::sync::Mutex::new(Some(original.clone()))); + let metadata_storage = FakeMetadata { + inner: metadata_inner, + }; + let l2_block_source = PendingL2BlockSource; + let new_exec_gas_price = 100; + let new_min_exec_gas_price = 99; + let new_exec_gas_price_change_percent = 88; + let new_l2_block_fullness_threshold_percent = 77; + let new_metadata = V0Metadata { + exec_gas_price_change_percent: new_exec_gas_price_change_percent, + new_exec_price: new_exec_gas_price, + l2_block_fullness_threshold_percent: new_l2_block_fullness_threshold_percent, + min_exec_gas_price: new_min_exec_gas_price, + l2_block_height: original.l2_block_height().into(), + }; + let (algo_updater, shared_algo) = + initialize_algorithm(new_metadata, &metadata_storage).unwrap(); + + // when + let service = GasPriceServiceV0::new( + l2_block_source, + metadata_storage, + shared_algo, + algo_updater, + ); + + // then + let expected = original; + let actual = service.algorithm_updater().clone().into(); + assert_ne!(expected, actual); +} + +#[tokio::test] +async fn initialize_algorithm__should_fail_if_cannot_fetch_metadata() { + // given + let metadata_storage = ErroringMetadata; + + // when + let metadata = different_arb_metadata(); + let res = initialize_algorithm(metadata, &metadata_storage); + + // then + assert!(matches!(res, Err(GasPriceError::CouldNotInitUpdater(_)))); +} diff --git a/crates/services/gas_price_service/src/v0/uninitialized_task.rs b/crates/services/gas_price_service/src/v0/uninitialized_task.rs new file mode 100644 index 00000000000..b21c82635a9 --- /dev/null +++ b/crates/services/gas_price_service/src/v0/uninitialized_task.rs @@ -0,0 +1,372 @@ +use crate::{ + common::{ + fuel_core_storage_adapter::{ + get_block_info, + storage::GasPriceColumn, + GasPriceSettings, + GasPriceSettingsProvider, + }, + gas_price_algorithm::SharedGasPriceAlgo, + l2_block_source::FuelL2BlockSource, + updater_metadata::UpdaterMetadata, + utils::{ + BlockInfo, + Error as GasPriceError, + Result as GasPriceResult, + }, + }, + ports::{ + GasPriceData, + GasPriceServiceConfig, + L2Data, + MetadataStorage, + }, + v0::{ + metadata::V0Metadata, + service::GasPriceServiceV0, + }, +}; +use fuel_core_services::{ + stream::BoxStream, + RunnableService, + ServiceRunner, + StateWatcher, +}; +use fuel_core_storage::{ + kv_store::KeyValueInspect, + not_found, + structured_storage::StructuredStorage, + transactional::{ + AtomicView, + Modifiable, + }, +}; +use fuel_core_types::{ + fuel_types::BlockHeight, + services::block_importer::SharedImportResult, +}; +use fuel_gas_price_algorithm::v0::AlgorithmUpdaterV0; + +pub use fuel_gas_price_algorithm::v0::AlgorithmV0; + +pub type SharedV0Algorithm = SharedGasPriceAlgo; + +pub struct UninitializedTask { + pub config: GasPriceServiceConfig, + pub genesis_block_height: BlockHeight, + pub settings: SettingsProvider, + pub gas_price_db: GasPriceStore, + pub on_chain_db: L2DataStoreView, + pub block_stream: BoxStream, + shared_algo: SharedV0Algorithm, + algo_updater: AlgorithmUpdaterV0, + metadata_storage: StructuredStorage, +} + +fn get_default_metadata( + config: &GasPriceServiceConfig, + latest_block_height: u32, +) -> V0Metadata { + V0Metadata { + new_exec_price: config.starting_gas_price.max(config.min_gas_price), + min_exec_gas_price: config.min_gas_price, + exec_gas_price_change_percent: config.gas_price_change_percent, + l2_block_height: latest_block_height, + l2_block_fullness_threshold_percent: config.gas_price_threshold_percent, + } +} + +impl + UninitializedTask +where + L2DataStore: L2Data, + L2DataStoreView: AtomicView, + GasPriceStore: + GasPriceData + Modifiable + KeyValueInspect + Clone, + SettingsProvider: GasPriceSettingsProvider, +{ + pub fn new( + config: GasPriceServiceConfig, + genesis_block_height: BlockHeight, + settings: SettingsProvider, + block_stream: BoxStream, + gas_price_db: GasPriceStore, + on_chain_db: L2DataStoreView, + ) -> anyhow::Result { + let latest_block_height: u32 = on_chain_db + .latest_view()? + .latest_height() + .unwrap_or(genesis_block_height) + .into(); + + let metadata_storage = StructuredStorage::new(gas_price_db.clone()); + let starting_metadata = get_default_metadata(&config, latest_block_height); + let (algo_updater, shared_algo) = + initialize_algorithm(starting_metadata, &metadata_storage)?; + + let task = Self { + config, + genesis_block_height, + settings, + gas_price_db, + on_chain_db, + block_stream, + algo_updater, + shared_algo, + metadata_storage, + }; + Ok(task) + } + + pub fn init( + mut self, + ) -> anyhow::Result< + GasPriceServiceV0< + FuelL2BlockSource, + StructuredStorage, + >, + > { + let mut first_run = false; + let latest_block_height: u32 = self + .on_chain_db + .latest_view()? + .latest_height() + .unwrap_or(self.genesis_block_height) + .into(); + + let maybe_metadata_height = self.gas_price_db.latest_height(); + let metadata_height = if let Some(metadata_height) = maybe_metadata_height { + metadata_height.into() + } else { + first_run = true; + latest_block_height + }; + + let l2_block_source = FuelL2BlockSource::new( + self.genesis_block_height, + self.settings.clone(), + self.block_stream, + ); + + if BlockHeight::from(latest_block_height) == self.genesis_block_height + || first_run + { + let service = GasPriceServiceV0::new( + l2_block_source, + self.metadata_storage, + self.shared_algo, + self.algo_updater, + ); + Ok(service) + } else { + if latest_block_height > metadata_height { + sync_gas_price_db_with_on_chain_storage( + &self.settings, + &mut self.metadata_storage, + &self.on_chain_db, + metadata_height, + latest_block_height, + )?; + } + + let service = GasPriceServiceV0::new( + l2_block_source, + self.metadata_storage, + self.shared_algo, + self.algo_updater, + ); + Ok(service) + } + } +} + +#[async_trait::async_trait] +impl RunnableService + for UninitializedTask +where + L2DataStore: L2Data, + L2DataStoreView: AtomicView, + GasPriceStore: + GasPriceData + Modifiable + KeyValueInspect + Clone, + SettingsProvider: GasPriceSettingsProvider, +{ + const NAME: &'static str = "UninitializedGasPriceServiceV0"; + type SharedData = SharedV0Algorithm; + type Task = GasPriceServiceV0< + FuelL2BlockSource, + StructuredStorage, + >; + type TaskParams = (); + + fn shared_data(&self) -> Self::SharedData { + self.shared_algo.clone() + } + + async fn into_task( + self, + _state_watcher: &StateWatcher, + _params: Self::TaskParams, + ) -> anyhow::Result { + UninitializedTask::init(self) + } +} + +pub fn initialize_algorithm( + starting_metadata: V0Metadata, + metadata_storage: &Metadata, +) -> GasPriceResult<(AlgorithmUpdaterV0, SharedV0Algorithm)> +where + Metadata: MetadataStorage, +{ + let V0Metadata { + min_exec_gas_price, + exec_gas_price_change_percent, + new_exec_price, + l2_block_fullness_threshold_percent, + l2_block_height, + } = starting_metadata; + + let algorithm_updater; + if let Some(old_metadata) = metadata_storage + .get_metadata(&l2_block_height.into()) + .map_err(|err| GasPriceError::CouldNotInitUpdater(anyhow::anyhow!(err)))? + { + algorithm_updater = match old_metadata { + UpdaterMetadata::V0(old) => AlgorithmUpdaterV0::new( + old.new_exec_price, + min_exec_gas_price, + exec_gas_price_change_percent, + old.l2_block_height, + l2_block_fullness_threshold_percent, + ), + }; + } else { + algorithm_updater = AlgorithmUpdaterV0::new( + new_exec_price, + min_exec_gas_price, + exec_gas_price_change_percent, + l2_block_height, + l2_block_fullness_threshold_percent, + ); + } + + let shared_algo = + SharedGasPriceAlgo::new_with_algorithm(algorithm_updater.algorithm()); + + Ok((algorithm_updater, shared_algo)) +} + +fn sync_gas_price_db_with_on_chain_storage< + L2DataStore, + L2DataStoreView, + GasPriceStore, + SettingsProvider, +>( + settings: &SettingsProvider, + metadata_storage: &mut StructuredStorage, + on_chain_db: &L2DataStoreView, + metadata_height: u32, + latest_block_height: u32, +) -> anyhow::Result<()> +where + L2DataStore: L2Data, + L2DataStoreView: AtomicView, + GasPriceStore: GasPriceData + Modifiable + KeyValueInspect, + SettingsProvider: GasPriceSettingsProvider, +{ + let UpdaterMetadata::V0(metadata) = metadata_storage + .get_metadata(&metadata_height.into())? + .ok_or(anyhow::anyhow!( + "Expected metadata to exist for height: {metadata_height}" + ))?; + + let mut algo_updater = metadata.into(); + + sync_v0_metadata( + settings, + on_chain_db, + metadata_height, + latest_block_height, + &mut algo_updater, + metadata_storage, + )?; + + Ok(()) +} + +fn sync_v0_metadata( + settings: &SettingsProvider, + on_chain_db: &L2DataStoreView, + metadata_height: u32, + latest_block_height: u32, + updater: &mut AlgorithmUpdaterV0, + metadata_storage: &mut StructuredStorage, +) -> anyhow::Result<()> +where + L2DataStore: L2Data, + L2DataStoreView: AtomicView, + GasPriceStore: GasPriceData + Modifiable + KeyValueInspect, + SettingsProvider: GasPriceSettingsProvider, +{ + let first = metadata_height.saturating_add(1); + let view = on_chain_db.latest_view()?; + for height in first..=latest_block_height { + let block = view + .get_block(&height.into())? + .ok_or(not_found!("FullBlock"))?; + let param_version = block.header().consensus_parameters_version; + + let GasPriceSettings { + gas_price_factor, + block_gas_limit, + } = settings.settings(¶m_version)?; + let block_gas_capacity = block_gas_limit.try_into()?; + + let block_gas_used = + match get_block_info(&block, gas_price_factor, block_gas_limit)? { + BlockInfo::GenesisBlock => { + Err(anyhow::anyhow!("should not be genesis block"))? + } + BlockInfo::Block { gas_used, .. } => gas_used, + }; + + updater.update_l2_block_data(height, block_gas_used, block_gas_capacity)?; + let metadata: UpdaterMetadata = updater.clone().into(); + metadata_storage.set_metadata(&metadata)?; + } + + Ok(()) +} + +pub fn new_gas_price_service_v0< + L2DataStore, + L2DataStoreView, + GasPriceStore, + SettingsProvider, +>( + config: GasPriceServiceConfig, + genesis_block_height: BlockHeight, + settings: SettingsProvider, + block_stream: BoxStream, + gas_price_db: GasPriceStore, + on_chain_db: L2DataStoreView, +) -> anyhow::Result< + ServiceRunner>, +> +where + L2DataStore: L2Data, + L2DataStoreView: AtomicView, + GasPriceStore: + GasPriceData + Modifiable + KeyValueInspect + Clone, + SettingsProvider: GasPriceSettingsProvider, +{ + let gas_price_init = UninitializedTask::new( + config, + genesis_block_height, + settings, + block_stream, + gas_price_db, + on_chain_db, + )?; + Ok(ServiceRunner::new(gas_price_init)) +} diff --git a/crates/services/gas_price_service/src/v1.rs b/crates/services/gas_price_service/src/v1.rs new file mode 100644 index 00000000000..534a31b6167 --- /dev/null +++ b/crates/services/gas_price_service/src/v1.rs @@ -0,0 +1,2 @@ +pub mod algorithm; +pub mod da_source_adapter; diff --git a/crates/services/gas_price_service/src/v1/algorithm.rs b/crates/services/gas_price_service/src/v1/algorithm.rs new file mode 100644 index 00000000000..92333d8cce6 --- /dev/null +++ b/crates/services/gas_price_service/src/v1/algorithm.rs @@ -0,0 +1,13 @@ +use crate::common::gas_price_algorithm::GasPriceAlgorithm; +use fuel_core_types::fuel_types::BlockHeight; +use fuel_gas_price_algorithm::v1::AlgorithmV1; + +impl GasPriceAlgorithm for AlgorithmV1 { + fn next_gas_price(&self) -> u64 { + self.calculate() + } + + fn worst_case_gas_price(&self, block_height: BlockHeight) -> u64 { + self.worst_case(block_height.into()) + } +} diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter.rs b/crates/services/gas_price_service/src/v1/da_source_adapter.rs similarity index 89% rename from crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter.rs rename to crates/services/gas_price_service/src/v1/da_source_adapter.rs index 8323077c07b..d984cdeed91 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter.rs +++ b/crates/services/gas_price_service/src/v1/da_source_adapter.rs @@ -1,12 +1,10 @@ -use crate::fuel_gas_price_updater::{ - da_source_adapter::service::{ +use crate::{ + common::utils::Result as GasPriceResult, + v1::da_source_adapter::service::{ new_service, DaBlockCostsService, DaBlockCostsSource, }, - DaBlockCosts, - GetDaBlockCosts, - Result as GasPriceUpdaterResult, }; use fuel_core_services::ServiceRunner; use std::{ @@ -24,6 +22,17 @@ pub mod service; pub const POLLING_INTERVAL_MS: u64 = 10_000; +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq)] +pub struct DaBlockCosts { + pub l2_block_range: core::ops::Range, + pub blob_size_bytes: u32, + pub blob_cost_wei: u128, +} + +pub trait GetDaBlockCosts: Send + Sync { + fn get(&self) -> GasPriceResult>; +} + #[derive(Clone)] pub struct DaBlockCostsSharedState { inner: Arc>>, @@ -59,7 +68,7 @@ where } impl GetDaBlockCosts for DaBlockCostsSharedState { - fn get(&self) -> GasPriceUpdaterResult> { + fn get(&self) -> GasPriceResult> { if let Ok(mut guard) = self.inner.try_lock() { if let Ok(da_block_costs) = guard.try_recv() { return Ok(Some(da_block_costs)); @@ -73,7 +82,7 @@ impl GetDaBlockCosts for DaBlockCostsSharedState { #[cfg(test)] mod tests { use super::*; - use crate::fuel_gas_price_updater::da_source_adapter::dummy_costs::DummyDaBlockCosts; + use crate::v1::da_source_adapter::dummy_costs::DummyDaBlockCosts; use fuel_core_services::Service; use std::time::Duration; use tokio::time::sleep; diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/block_committer_costs.rs b/crates/services/gas_price_service/src/v1/da_source_adapter/block_committer_costs.rs similarity index 95% rename from crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/block_committer_costs.rs rename to crates/services/gas_price_service/src/v1/da_source_adapter/block_committer_costs.rs index 9bc6603cbfe..7c0beaaffbf 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/block_committer_costs.rs +++ b/crates/services/gas_price_service/src/v1/da_source_adapter/block_committer_costs.rs @@ -1,5 +1,5 @@ -use crate::fuel_gas_price_updater::{ - da_source_adapter::service::{ +use crate::v1::da_source_adapter::{ + service::{ DaBlockCostsSource, Result as DaBlockCostsResult, }, diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/dummy_costs.rs b/crates/services/gas_price_service/src/v1/da_source_adapter/dummy_costs.rs similarity index 89% rename from crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/dummy_costs.rs rename to crates/services/gas_price_service/src/v1/da_source_adapter/dummy_costs.rs index 35f0b300d90..d962db83f56 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/dummy_costs.rs +++ b/crates/services/gas_price_service/src/v1/da_source_adapter/dummy_costs.rs @@ -1,5 +1,5 @@ -use crate::fuel_gas_price_updater::{ - da_source_adapter::service::{ +use crate::v1::da_source_adapter::{ + service::{ DaBlockCostsSource, Result as DaBlockCostsResult, }, diff --git a/crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/service.rs b/crates/services/gas_price_service/src/v1/da_source_adapter/service.rs similarity index 97% rename from crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/service.rs rename to crates/services/gas_price_service/src/v1/da_source_adapter/service.rs index 23fc9866120..c87defa52f5 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/da_source_adapter/service.rs +++ b/crates/services/gas_price_service/src/v1/da_source_adapter/service.rs @@ -1,7 +1,3 @@ -use crate::fuel_gas_price_updater::{ - da_source_adapter::POLLING_INTERVAL_MS, - DaBlockCosts, -}; use fuel_core_services::{ RunnableService, RunnableTask, @@ -20,6 +16,10 @@ use tokio::{ }, }; +use crate::v1::da_source_adapter::{ + DaBlockCosts, + POLLING_INTERVAL_MS, +}; pub use anyhow::Result; /// This struct houses the shared_state, polling interval diff --git a/tests/tests/gas_price.rs b/tests/tests/gas_price.rs index a164f3ad9da..ad982886c74 100644 --- a/tests/tests/gas_price.rs +++ b/tests/tests/gas_price.rs @@ -19,10 +19,12 @@ use fuel_core_client::client::{ types::gas_price::LatestGasPrice, FuelClient, }; -use fuel_core_gas_price_service::fuel_gas_price_updater::{ - fuel_core_storage_adapter::storage::GasPriceMetadata, - UpdaterMetadata, - V0Metadata, +use fuel_core_gas_price_service::{ + common::{ + fuel_core_storage_adapter::storage::GasPriceMetadata, + updater_metadata::UpdaterMetadata, + }, + v0::metadata::V0Metadata, }; use fuel_core_poa::Trigger; use fuel_core_storage::{