diff --git a/CHANGELOG.md b/CHANGELOG.md index fd9152bd03a..67b67b37b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Description of the upcoming release here. ### Added +- [#1752](https://github.com/FuelLabs/fuel-core/pull/1752): Add `ProducerGasPrice` trait that the `Producer` depends on to get the gas price for the block. - [#1747](https://github.com/FuelLabs/fuel-core/pull/1747): The DA block height is now included in the genesis state. - [#1740](https://github.com/FuelLabs/fuel-core/pull/1740): Remove optional fields from genesis configs - [#1737](https://github.com/FuelLabs/fuel-core/pull/1737): Remove temporary tables for calculating roots during genesis. diff --git a/benches/benches/block_target_gas.rs b/benches/benches/block_target_gas.rs index 362a6220043..cb1bc1617b9 100644 --- a/benches/benches/block_target_gas.rs +++ b/benches/benches/block_target_gas.rs @@ -419,7 +419,7 @@ fn run_with_service_with_extra_inputs( let mut sub = shared.block_importer.block_importer.subscribe(); shared - .txpool + .txpool_shared_state .insert(vec![std::sync::Arc::new(tx)]) .await .into_iter() diff --git a/benches/benches/transaction_throughput.rs b/benches/benches/transaction_throughput.rs index c495f1b1343..e61130a2f46 100644 --- a/benches/benches/transaction_throughput.rs +++ b/benches/benches/transaction_throughput.rs @@ -105,7 +105,7 @@ where test_builder.finalize().await; // insert all transactions - srv.shared.txpool.insert(transactions).await; + srv.shared.txpool_shared_state.insert(transactions).await; let _ = client.produce_blocks(1, None).await; // sanity check block to ensure the transactions were actually processed diff --git a/bin/e2e-test-client/tests/integration_tests.rs b/bin/e2e-test-client/tests/integration_tests.rs index a8b2ed4f8be..1221bf4bcd3 100644 --- a/bin/e2e-test-client/tests/integration_tests.rs +++ b/bin/e2e-test-client/tests/integration_tests.rs @@ -117,7 +117,7 @@ fn dev_config() -> Config { ); config.chain_config = chain_config; - config.block_producer.gas_price = 1; + config.static_gas_price = 1; config.state_reader = StateReader::in_memory(state_config); config.block_producer.coinbase_recipient = Some( diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index 0b846b5f594..342263cd37c 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -344,7 +344,6 @@ impl Command { tx_max_number, tx_max_depth, chain_conf, - min_gas_price, utxo_validation, metrics, tx_pool_ttl.into(), @@ -354,9 +353,9 @@ impl Command { block_producer: ProducerConfig { utxo_validation, coinbase_recipient, - gas_price: min_gas_price, metrics, }, + static_gas_price: min_gas_price, block_importer, #[cfg(feature = "relayer")] relayer: relayer_cfg, diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index a5b2f4bc9c2..151b7e935fa 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -667,7 +667,6 @@ type Mutation { type NodeInfo { utxoValidation: Boolean! vmBacktrace: Boolean! - minGasPrice: U64! maxTx: U64! maxDepth: U64! nodeVersion: String! diff --git a/crates/client/src/client/schema/node_info.rs b/crates/client/src/client/schema/node_info.rs index 018551e11af..30037a6e916 100644 --- a/crates/client/src/client/schema/node_info.rs +++ b/crates/client/src/client/schema/node_info.rs @@ -20,7 +20,6 @@ use std::{ pub struct NodeInfo { pub utxo_validation: bool, pub vm_backtrace: bool, - pub min_gas_price: U64, pub max_tx: U64, pub max_depth: U64, pub node_version: String, diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__node_info__tests__node_info_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__node_info__tests__node_info_query_gql_output.snap index c2d466d7f83..1d538f92ad0 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__node_info__tests__node_info_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__node_info__tests__node_info_query_gql_output.snap @@ -6,7 +6,6 @@ query { nodeInfo { utxoValidation vmBacktrace - minGasPrice maxTx maxDepth nodeVersion diff --git a/crates/client/src/client/types/node_info.rs b/crates/client/src/client/types/node_info.rs index e0415508e9b..3d9d7e36aa3 100644 --- a/crates/client/src/client/types/node_info.rs +++ b/crates/client/src/client/types/node_info.rs @@ -3,7 +3,6 @@ use crate::client::schema; pub struct NodeInfo { pub utxo_validation: bool, pub vm_backtrace: bool, - pub min_gas_price: u64, pub max_tx: u64, pub max_depth: u64, pub node_version: String, @@ -16,7 +15,6 @@ impl From for NodeInfo { Self { utxo_validation: value.utxo_validation, vm_backtrace: value.vm_backtrace, - min_gas_price: value.min_gas_price.into(), max_tx: value.max_tx.into(), max_depth: value.max_depth.into(), node_version: value.node_version, diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index 34eb81e2c28..119016ca586 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -23,7 +23,6 @@ pub struct Config { pub utxo_validation: bool, pub debug: bool, pub vm_backtrace: bool, - pub min_gas_price: u64, pub max_tx: usize, pub max_depth: usize, pub chain_name: String, diff --git a/crates/fuel-core/src/graphql_api/api_service.rs b/crates/fuel-core/src/graphql_api/api_service.rs index 16f9522f59b..222508af862 100644 --- a/crates/fuel-core/src/graphql_api/api_service.rs +++ b/crates/fuel-core/src/graphql_api/api_service.rs @@ -4,6 +4,7 @@ use crate::{ ports::{ BlockProducerPort, ConsensusModulePort, + GasPriceEstimate, OffChainDatabase, OnChainDatabase, P2pPort, @@ -88,6 +89,8 @@ pub type TxPool = Box; pub type ConsensusModule = Box; pub type P2pService = Box; +pub type GasPriceProvider = Box; + #[derive(Clone)] pub struct SharedState { pub bound_address: SocketAddr, @@ -173,6 +176,7 @@ pub fn new_service( producer: BlockProducer, consensus_module: ConsensusModule, p2p_service: P2pService, + gas_price_provider: GasPriceProvider, log_threshold_ms: Duration, request_timeout: Duration, ) -> anyhow::Result @@ -192,6 +196,7 @@ where .data(producer) .data(consensus_module) .data(p2p_service) + .data(gas_price_provider) .extension(async_graphql::extensions::Tracing) .extension(MetricsExtension::new(log_threshold_ms)) .extension(ViewExtension::new()) diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index 069852e5964..91736b18a95 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -197,6 +197,13 @@ pub trait P2pPort: Send + Sync { async fn all_peer_info(&self) -> anyhow::Result>; } +/// Trait for defining how to estimate gas price for future blocks +#[async_trait::async_trait] +pub trait GasPriceEstimate: Send + Sync { + /// The worst case scenario for gas price at a given horizon + async fn worst_case_gas_price(&self, height: BlockHeight) -> u64; +} + pub mod worker { use super::super::storage::blocks::FuelBlockIdsToHeights; use crate::fuel_core_graphql_api::storage::{ diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index 318b716ef8d..bbb7746e973 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -472,7 +472,7 @@ impl Node { let tx_result = self .node .shared - .txpool + .txpool_shared_state .insert(vec![Arc::new(tx.clone())]) .await .pop() diff --git a/crates/fuel-core/src/schema/gas_price.rs b/crates/fuel-core/src/schema/gas_price.rs index 29e80dae1a7..1d78cbc40c7 100644 --- a/crates/fuel-core/src/schema/gas_price.rs +++ b/crates/fuel-core/src/schema/gas_price.rs @@ -3,17 +3,24 @@ use super::scalars::{ U64, }; use crate::{ - fuel_core_graphql_api::{ - database::ReadView, - Config as GraphQLConfig, + fuel_core_graphql_api::database::ReadView, + graphql_api::api_service::GasPriceProvider, + query::{ + BlockQueryData, + SimpleTransactionData, }, - query::BlockQueryData, }; use async_graphql::{ Context, Object, }; -use fuel_core_types::blockchain::block::Block; +use fuel_core_types::{ + blockchain::block::Block, + fuel_tx::{ + field::MintGasPrice, + Transaction, + }, +}; pub struct LatestGasPrice { pub gas_price: U64, @@ -40,14 +47,18 @@ impl LatestGasPriceQuery { &self, ctx: &Context<'_>, ) -> async_graphql::Result { - let config = ctx.data_unchecked::(); - let query: &ReadView = ctx.data_unchecked(); - let latest_block: Block<_> = query.latest_block()?; - let block_height = u32::from(*latest_block.header().height()); + let latest_block: Block<_> = query.latest_block()?; + let block_height: u32 = (*latest_block.header().height()).into(); + let mut gas_price: U64 = 0.into(); + if let Some(tx_id) = latest_block.transactions().last() { + if let Transaction::Mint(mint_tx) = query.transaction(tx_id)? { + gas_price = (*mint_tx.gas_price()).into(); + } + } Ok(LatestGasPrice { - gas_price: config.min_gas_price.into(), + gas_price, block_height: block_height.into(), }) } @@ -77,13 +88,22 @@ impl EstimateGasPriceQuery { )] block_horizon: Option, ) -> async_graphql::Result { - // TODO: implement dynamic calculation based on block horizon - // https://github.com/FuelLabs/fuel-core/issues/1653 - let _ = block_horizon; + let query: &ReadView = ctx.data_unchecked(); - let config = ctx.data_unchecked::(); - let gas_price = config.min_gas_price.into(); + let latest_block_height: u32 = query.latest_block_height()?.into(); + let target_block = block_horizon + .and_then(|h| h.0.checked_add(latest_block_height)) + .ok_or(async_graphql::Error::new(format!( + "Invalid block horizon. Overflows latest block :{latest_block_height:?}" + )))?; - Ok(EstimateGasPrice { gas_price }) + let gas_price_provider = ctx.data_unchecked::(); + let gas_price = gas_price_provider + .worst_case_gas_price(target_block.into()) + .await; + + Ok(EstimateGasPrice { + gas_price: gas_price.into(), + }) } } diff --git a/crates/fuel-core/src/schema/node_info.rs b/crates/fuel-core/src/schema/node_info.rs index 647b0c4215e..1d8472d476f 100644 --- a/crates/fuel-core/src/schema/node_info.rs +++ b/crates/fuel-core/src/schema/node_info.rs @@ -12,7 +12,6 @@ use std::time::UNIX_EPOCH; pub struct NodeInfo { utxo_validation: bool, vm_backtrace: bool, - min_gas_price: U64, max_tx: U64, max_depth: U64, node_version: String, @@ -28,10 +27,6 @@ impl NodeInfo { self.vm_backtrace } - async fn min_gas_price(&self) -> U64 { - self.min_gas_price - } - async fn max_tx(&self) -> U64 { self.max_tx } @@ -75,7 +70,6 @@ impl NodeQuery { Ok(NodeInfo { utxo_validation: config.utxo_validation, vm_backtrace: config.vm_backtrace, - min_gas_price: config.min_gas_price.into(), max_tx: (config.max_tx as u64).into(), max_depth: (config.max_depth as u64).into(), node_version: VERSION.to_owned(), diff --git a/crates/fuel-core/src/service.rs b/crates/fuel-core/src/service.rs index b976ec96de5..4bb003ecad2 100644 --- a/crates/fuel-core/src/service.rs +++ b/crates/fuel-core/src/service.rs @@ -24,6 +24,7 @@ use fuel_core_storage::{ }; use std::net::SocketAddr; +use crate::service::adapters::StaticGasPrice; pub use config::{ Config, DbType, @@ -44,7 +45,8 @@ pub struct SharedState { /// The PoA adaptor around the shared state of the consensus module. pub poa_adapter: PoAAdapter, /// The transaction pool shared state. - pub txpool: fuel_core_txpool::service::SharedState, + pub txpool_shared_state: + fuel_core_txpool::service::SharedState, /// The P2P network shared state. #[cfg(feature = "p2p")] pub network: Option, diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index a688439c715..58f0405f1e7 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -33,6 +33,17 @@ pub mod relayer; pub mod sync; pub mod txpool; +#[derive(Debug, Clone)] +pub struct StaticGasPrice { + pub gas_price: u64, +} + +impl StaticGasPrice { + pub fn new(gas_price: u64) -> Self { + Self { gas_price } + } +} + #[derive(Clone)] pub struct PoAAdapter { shared_state: Option, @@ -40,24 +51,24 @@ pub struct PoAAdapter { #[derive(Clone)] pub struct TxPoolAdapter { - service: TxPoolSharedState, + service: TxPoolSharedState, } impl TxPoolAdapter { - pub fn new(service: TxPoolSharedState) -> Self { + pub fn new(service: TxPoolSharedState) -> Self { Self { service } } } #[derive(Clone)] pub struct TransactionsSource { - txpool: TxPoolSharedState, + txpool: TxPoolSharedState, _block_height: BlockHeight, } impl TransactionsSource { pub fn new( - txpool: TxPoolSharedState, + txpool: TxPoolSharedState, block_height: BlockHeight, ) -> Self { Self { diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index b3ef02fc74c..0551884f9db 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -1,6 +1,7 @@ use super::{ BlockImporterAdapter, BlockProducerAdapter, + StaticGasPrice, }; use crate::{ database::Database, @@ -8,6 +9,7 @@ use crate::{ worker, BlockProducerPort, DatabaseMessageProof, + GasPriceEstimate, P2pPort, TxPoolPort, }, @@ -161,3 +163,10 @@ impl worker::TxPool for TxPoolAdapter { self.service.send_complete(id, block_height, status) } } + +#[async_trait::async_trait] +impl GasPriceEstimate for StaticGasPrice { + async fn worst_case_gas_price(&self, _height: BlockHeight) -> u64 { + self.gas_price + } +} diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index 573bc66879d..3c131a96d2d 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -5,6 +5,7 @@ use crate::{ BlockProducerAdapter, ExecutorAdapter, MaybeRelayerAdapter, + StaticGasPrice, TransactionsSource, TxPoolAdapter, }, @@ -12,7 +13,13 @@ use crate::{ }, }; use fuel_core_executor::executor::OnceTransactionsSource; -use fuel_core_producer::ports::TxPool; +use fuel_core_producer::{ + block_producer::gas_price::{ + GasPriceParams, + GasPriceProvider, + }, + ports::TxPool, +}; use fuel_core_storage::{ not_found, tables::FuelBlocks, @@ -140,3 +147,9 @@ impl fuel_core_producer::ports::BlockProducerDatabase for Database { self.storage::().root(height).map(Into::into) } } + +impl GasPriceProvider for StaticGasPrice { + fn gas_price(&self, _block_height: GasPriceParams) -> Option { + Some(self.gas_price) + } +} diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index 02914e0f5db..6b243a3b91b 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -3,6 +3,7 @@ use crate::{ service::adapters::{ BlockImporterAdapter, P2PAdapter, + StaticGasPrice, }, }; use fuel_core_services::stream::BoxStream; @@ -16,7 +17,10 @@ use fuel_core_storage::{ Result as StorageResult, StorageAsRef, }; -use fuel_core_txpool::ports::BlockImporter; +use fuel_core_txpool::{ + ports::BlockImporter, + txpool::GasPriceProvider, +}; use fuel_core_types::{ entities::{ coins::coin::CompressedCoin, @@ -27,6 +31,7 @@ use fuel_core_types::{ UtxoId, }, fuel_types::{ + BlockHeight, ContractId, Nonce, }, @@ -132,3 +137,9 @@ impl fuel_core_txpool::ports::TxPoolDb for Database { self.storage::().contains_key(id) } } + +impl GasPriceProvider for StaticGasPrice { + fn gas_price(&self, _block_height: BlockHeight) -> Option { + Some(self.gas_price) + } +} diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index b51b26553b3..f81713bc582 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -55,6 +55,7 @@ pub struct Config { pub vm: VMConfig, pub txpool: fuel_core_txpool::Config, pub block_producer: fuel_core_producer::Config, + pub static_gas_price: u64, pub block_importer: fuel_core_importer::Config, #[cfg(feature = "relayer")] pub relayer: Option, @@ -105,15 +106,14 @@ impl Config { utxo_validation, txpool: fuel_core_txpool::Config { chain_config, - min_gas_price, utxo_validation, transaction_ttl: Duration::from_secs(60 * 100000000), ..fuel_core_txpool::Config::default() }, block_producer: fuel_core_producer::Config { - gas_price: min_gas_price, ..Default::default() }, + static_gas_price: min_gas_price, block_importer, #[cfg(feature = "relayer")] relayer: None, @@ -144,15 +144,6 @@ impl Config { self.txpool.chain_config = self.chain_config.clone(); } - if self.txpool.min_gas_price != self.block_producer.gas_price { - tracing::warn!( - "The `min_gas_price` of `TxPool` was inconsistent with `BlockProducer`" - ); - let min_gas_price = - core::cmp::max(self.txpool.min_gas_price, self.block_producer.gas_price); - self.txpool.min_gas_price = min_gas_price; - self.block_producer.gas_price = min_gas_price; - } if self.txpool.utxo_validation != self.utxo_validation { tracing::warn!("The `utxo_validation` of `TxPool` was inconsistent"); self.txpool.utxo_validation = self.utxo_validation; diff --git a/crates/fuel-core/src/service/query.rs b/crates/fuel-core/src/service/query.rs index 2144a20cf3f..15b915daffb 100644 --- a/crates/fuel-core/src/service/query.rs +++ b/crates/fuel-core/src/service/query.rs @@ -26,7 +26,7 @@ impl FuelService { pub async fn submit(&self, tx: Transaction) -> anyhow::Result { let results: Vec<_> = self .shared - .txpool + .txpool_shared_state .insert(vec![Arc::new(tx)]) .await .into_iter() @@ -80,7 +80,7 @@ impl FuelService { &self, id: Bytes32, ) -> anyhow::Result>> { - let txpool = self.shared.txpool.clone(); + let txpool = self.shared.txpool_shared_state.clone(); let db = self.shared.database.off_chain().clone(); let rx = txpool.tx_update_subscribe(id)?; Ok(transaction_status_change( diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 3840b048152..9a7644070d1 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -28,6 +28,7 @@ use tokio::sync::Mutex; #[cfg(feature = "relayer")] use crate::relayer::Config as RelayerConfig; +use crate::service::StaticGasPrice; #[cfg(feature = "relayer")] use fuel_core_types::blockchain::primitives::DaBlockHeight; @@ -35,11 +36,12 @@ pub type PoAService = fuel_core_poa::Service; #[cfg(feature = "p2p")] pub type P2PService = fuel_core_p2p::service::Service; -pub type TxPoolService = fuel_core_txpool::Service; +pub type TxPoolService = fuel_core_txpool::Service; pub type BlockProducerService = fuel_core_producer::block_producer::Producer< Database, TxPoolAdapter, ExecutorAdapter, + StaticGasPrice, >; pub type GraphQL = fuel_core_graphql_api::api_service::Service; @@ -132,12 +134,14 @@ pub fn init_sub_services( #[cfg(not(feature = "p2p"))] let p2p_adapter = P2PAdapter::new(); + let gas_price_provider = StaticGasPrice::new(config.static_gas_price); let txpool = fuel_core_txpool::new_service( config.txpool.clone(), database.on_chain().clone(), importer_adapter.clone(), p2p_adapter.clone(), last_height, + gas_price_provider.clone(), ); let tx_pool_adapter = TxPoolAdapter::new(txpool.shared.clone()); @@ -148,6 +152,7 @@ pub fn init_sub_services( executor: Arc::new(executor), relayer: Box::new(relayer_adapter.clone()), lock: Mutex::new(()), + gas_price_provider: gas_price_provider.clone(), }; let producer_adapter = BlockProducerAdapter::new(block_producer); @@ -203,7 +208,6 @@ pub fn init_sub_services( utxo_validation: config.utxo_validation, debug: config.debug, vm_backtrace: config.vm.backtrace, - min_gas_price: config.txpool.min_gas_price, max_tx: config.txpool.max_tx, max_depth: config.txpool.max_depth, chain_name: config.chain_config.chain_name.clone(), @@ -220,13 +224,14 @@ pub fn init_sub_services( Box::new(producer_adapter), Box::new(poa_adapter.clone()), Box::new(p2p_adapter), + Box::new(gas_price_provider), config.query_log_threshold_time, config.api_request_timeout, )?; let shared = SharedState { poa_adapter, - txpool: txpool.shared.clone(), + txpool_shared_state: txpool.shared.clone(), #[cfg(feature = "p2p")] network: network.as_ref().map(|n| n.shared.clone()), #[cfg(feature = "relayer")] diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index 7326b647d21..a03914cdd53 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -1,4 +1,5 @@ use crate::{ + block_producer::gas_price::GasPriceProvider as GasPriceProviderConstraint, ports, ports::BlockProducerDatabase, Config, @@ -42,6 +43,8 @@ use tracing::debug; #[cfg(test)] mod tests; +pub mod gas_price; + #[derive(Debug, derive_more::Display)] pub enum Error { #[display(fmt = "Genesis block is absent")] @@ -70,7 +73,7 @@ impl From for anyhow::Error { } } -pub struct Producer { +pub struct Producer { pub config: Config, pub view_provider: ViewProvider, pub txpool: TxPool, @@ -79,12 +82,15 @@ pub struct Producer { // use a tokio lock since we want callers to yield until the previous block // execution has completed (which may take a while). pub lock: Mutex<()>, + pub gas_price_provider: GasPriceProvider, } -impl Producer +impl + Producer where ViewProvider: AtomicView + 'static, ViewProvider::View: BlockProducerDatabase, + GasPriceProvider: GasPriceProviderConstraint, { /// Produces and execute block for the specified height. async fn produce_and_execute( @@ -112,11 +118,15 @@ where let header = self.new_header(height, block_time).await?; + let gas_price = self + .gas_price_provider + .gas_price(height.into()) + .ok_or(anyhow!("No gas price found for block {height:?}"))?; + let component = Components { header_to_produce: header, transactions_source: source, - // TODO: Provide gas price https://github.com/FuelLabs/fuel-core/issues/1642 - gas_price: self.config.gas_price, + gas_price, gas_limit: max_gas, }; @@ -134,12 +144,14 @@ where } } -impl Producer +impl + Producer where ViewProvider: AtomicView + 'static, ViewProvider::View: BlockProducerDatabase, TxPool: ports::TxPool + 'static, Executor: ports::Executor + 'static, + GasPriceProvider: GasPriceProviderConstraint, { /// Produces and execute block for the specified height with transactions from the `TxPool`. pub async fn produce_and_execute_block_txpool( @@ -158,11 +170,13 @@ where } } -impl Producer +impl + Producer where ViewProvider: AtomicView + 'static, ViewProvider::View: BlockProducerDatabase, Executor: ports::Executor> + 'static, + GasPriceProvider: GasPriceProviderConstraint, { /// Produces and execute block for the specified height with `transactions`. pub async fn produce_and_execute_block_transactions( @@ -177,11 +191,13 @@ where } } -impl Producer +impl + Producer where ViewProvider: AtomicView + 'static, ViewProvider::View: BlockProducerDatabase, Executor: ports::DryRunner + 'static, + GasPriceProvider: GasPriceProviderConstraint, { // TODO: Support custom `block_time` for `dry_run`. /// Simulates multiple transactions without altering any state. Does not acquire the production lock. @@ -201,6 +217,11 @@ where .expect("It is impossible to overflow the current block height") }); + let gas_price = self + .gas_price_provider + .gas_price(height.into()) + .ok_or(anyhow!("No gas price found for height {height:?}"))?; + // The dry run execution should use the state of the blockchain based on the // last available block, not on the upcoming one. It means that we need to // use the same configuration as the last block -> the same DA height. @@ -210,8 +231,7 @@ where let component = Components { header_to_produce: header, transactions_source: transactions.clone(), - // TODO: Provide gas price https://github.com/FuelLabs/fuel-core/issues/1642 - gas_price: self.config.gas_price, + gas_price, gas_limit: u64::MAX, }; @@ -239,7 +259,7 @@ where } } -impl Producer +impl Producer where ViewProvider: AtomicView + 'static, ViewProvider::View: BlockProducerDatabase, diff --git a/crates/services/producer/src/block_producer/gas_price.rs b/crates/services/producer/src/block_producer/gas_price.rs new file mode 100644 index 00000000000..c5d186481c2 --- /dev/null +++ b/crates/services/producer/src/block_producer/gas_price.rs @@ -0,0 +1,29 @@ +use fuel_core_types::fuel_types::BlockHeight; + +/// The parameters required to retrieve the gas price for a block +pub struct GasPriceParams { + block_height: BlockHeight, +} + +impl GasPriceParams { + /// Create a new `GasPriceParams` instance + pub fn new(block_height: BlockHeight) -> Self { + Self { block_height } + } + + pub fn block_height(&self) -> BlockHeight { + self.block_height + } +} + +impl From for GasPriceParams { + fn from(block_height: BlockHeight) -> Self { + Self { block_height } + } +} + +/// Interface for retrieving the gas price for a block +pub trait GasPriceProvider { + /// The gas price for all transactions in the block. + fn gas_price(&self, params: GasPriceParams) -> Option; +} diff --git a/crates/services/producer/src/block_producer/tests.rs b/crates/services/producer/src/block_producer/tests.rs index 8d68db2eea1..41caa0af323 100644 --- a/crates/services/producer/src/block_producer/tests.rs +++ b/crates/services/producer/src/block_producer/tests.rs @@ -1,9 +1,17 @@ +#![allow(non_snake_case)] use crate::{ - block_producer::Error, + block_producer::{ + gas_price::{ + GasPriceParams, + GasPriceProvider, + }, + Error, + }, mocks::{ FailingMockExecutor, MockDb, MockExecutor, + MockExecutorWithCapture, MockRelayer, MockTxPool, }, @@ -36,6 +44,28 @@ use std::sync::{ Mutex, }; +pub struct MockProducerGasPrice { + pub gas_price: Option, +} + +impl MockProducerGasPrice { + pub fn new(gas_price: u64) -> Self { + Self { + gas_price: Some(gas_price), + } + } + + pub fn new_none() -> Self { + Self { gas_price: None } + } +} + +impl GasPriceProvider for MockProducerGasPrice { + fn gas_price(&self, _params: GasPriceParams) -> Option { + self.gas_price + } +} + #[tokio::test] async fn cant_produce_at_genesis_height() { let ctx = TestContext::default(); @@ -203,12 +233,58 @@ async fn production_fails_on_execution_error() { ); } +// TODO: Add test that checks the gas price on the mint tx after `Executor` refactor +// https://github.com/FuelLabs/fuel-core/issues/1751 +#[tokio::test] +async fn produce_and_execute_block_txpool__executor_receives_gas_price_provided() { + // given + let gas_price = 1_000; + let gas_price_provider = MockProducerGasPrice::new(gas_price); + let executor = MockExecutorWithCapture::default(); + let ctx = TestContext::default_from_executor(executor.clone()); + + let producer = ctx.producer_with_gas_price_provider(gas_price_provider); + + // when + let _ = producer + .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) + .await + .unwrap(); + + // then + let captured = executor.captured.lock().unwrap(); + let expected = gas_price; + let actual = captured + .as_ref() + .expect("expected executor to be called") + .gas_price; + assert_eq!(expected, actual); +} + +#[tokio::test] +async fn produce_and_execute_block_txpool__missing_gas_price_causes_block_production_to_fail( +) { + // given + let gas_price_provider = MockProducerGasPrice::new_none(); + let ctx = TestContext::default(); + let producer = ctx.producer_with_gas_price_provider(gas_price_provider); + + // when + let result = producer + .produce_and_execute_block_txpool(1u32.into(), Tai64::now(), 1_000_000_000) + .await; + + // then + assert!(result.is_err()); +} + struct TestContext { config: Config, db: MockDb, relayer: MockRelayer, executor: Arc, txpool: MockTxPool, + gas_price: u64, } impl TestContext { @@ -242,16 +318,32 @@ impl TestContext { let txpool = MockTxPool::default(); let relayer = MockRelayer::default(); let config = Config::default(); + let gas_price = 0; Self { config, db, relayer, executor: Arc::new(executor), txpool, + gas_price, } } - pub fn producer(self) -> Producer { + pub fn producer( + self, + ) -> Producer { + let gas_price = self.gas_price; + let static_gas_price = MockProducerGasPrice::new(gas_price); + self.producer_with_gas_price_provider(static_gas_price) + } + + pub fn producer_with_gas_price_provider( + self, + gas_price_provider: GasPrice, + ) -> Producer + where + GasPrice: GasPriceProvider, + { Producer { config: self.config, view_provider: self.db, @@ -259,6 +351,7 @@ impl TestContext { executor: self.executor, relayer: Box::new(self.relayer), lock: Default::default(), + gas_price_provider, } } } diff --git a/crates/services/producer/src/config.rs b/crates/services/producer/src/config.rs index 4345b84b239..71efd451a14 100644 --- a/crates/services/producer/src/config.rs +++ b/crates/services/producer/src/config.rs @@ -4,6 +4,5 @@ use fuel_core_types::fuel_types::ContractId; pub struct Config { pub utxo_validation: bool, pub coinbase_recipient: Option, - pub gas_price: u64, pub metrics: bool, } diff --git a/crates/services/producer/src/mocks.rs b/crates/services/producer/src/mocks.rs index 1409bbabc6b..b909ed588e7 100644 --- a/crates/services/producer/src/mocks.rs +++ b/crates/services/producer/src/mocks.rs @@ -110,13 +110,14 @@ impl AsRef for MockDb { } } -fn to_block(component: Components>) -> Block { +fn to_block(component: &Components>) -> Block { let transactions = component .transactions_source + .clone() .into_iter() .map(|tx| tx.as_ref().into()) .collect(); - Block::new(component.header_to_produce, transactions, &[]) + Block::new(component.header_to_produce.clone(), transactions, &[]) } impl Executor> for MockExecutor { @@ -124,7 +125,7 @@ impl Executor> for MockExecutor { &self, component: Components>, ) -> ExecutorResult> { - let block = to_block(component); + let block = to_block(&component); // simulate executor inserting a block let mut block_db = self.0.blocks.lock().unwrap(); block_db.insert( @@ -155,7 +156,7 @@ impl Executor> for FailingMockExecutor { if let Some(err) = err.take() { Err(err) } else { - let block = to_block(component); + let block = to_block(&component); Ok(UncommittedResult::new( ExecutionResult { block, @@ -169,6 +170,38 @@ impl Executor> for FailingMockExecutor { } } +#[derive(Clone)] +pub struct MockExecutorWithCapture { + pub captured: Arc>>>>, +} + +impl Executor> for MockExecutorWithCapture { + fn execute_without_commit( + &self, + component: Components>, + ) -> ExecutorResult> { + let block = to_block(&component); + *self.captured.lock().unwrap() = Some(component); + Ok(UncommittedResult::new( + ExecutionResult { + block, + skipped_transactions: vec![], + tx_status: vec![], + events: vec![], + }, + Default::default(), + )) + } +} + +impl Default for MockExecutorWithCapture { + fn default() -> Self { + Self { + captured: Arc::new(Mutex::new(None)), + } + } +} + #[derive(Clone, Default, Debug)] pub struct MockDb { pub blocks: Arc>>, diff --git a/crates/services/txpool/src/config.rs b/crates/services/txpool/src/config.rs index 6c6a46d0525..d14033d65c0 100644 --- a/crates/services/txpool/src/config.rs +++ b/crates/services/txpool/src/config.rs @@ -62,8 +62,6 @@ pub struct Config { pub max_tx: usize, /// max depth of connected UTXO excluding contracts pub max_depth: usize, - /// The minimum allowed gas price - pub min_gas_price: u64, /// Flag to disable utxo existence and signature checks pub utxo_validation: bool, /// chain config @@ -82,7 +80,6 @@ impl Default for Config { fn default() -> Self { let max_tx = 4064; let max_depth = 10; - let min_gas_price = 0; let utxo_validation = true; let metrics = false; // 5 minute TTL @@ -92,7 +89,6 @@ impl Default for Config { max_tx, max_depth, ChainConfig::default(), - min_gas_price, utxo_validation, metrics, transaction_ttl, @@ -108,7 +104,6 @@ impl Config { max_tx: usize, max_depth: usize, chain_config: ChainConfig, - min_gas_price: u64, utxo_validation: bool, metrics: bool, transaction_ttl: Duration, @@ -120,7 +115,6 @@ impl Config { Self { max_tx, max_depth, - min_gas_price, utxo_validation, chain_config, metrics, diff --git a/crates/services/txpool/src/service.rs b/crates/services/txpool/src/service.rs index 9758cfaee15..f6ac475c35c 100644 --- a/crates/services/txpool/src/service.rs +++ b/crates/services/txpool/src/service.rs @@ -8,6 +8,7 @@ use crate::{ txpool::{ check_single_tx, check_transactions, + GasPriceProvider as GasPriceProviderConstraint, }, Config, Error as TxPoolError, @@ -72,7 +73,7 @@ use self::update_sender::{ mod update_sender; -pub type Service = ServiceRunner>; +pub type Service = ServiceRunner>; #[derive(Clone)] pub struct TxStatusChange { @@ -120,16 +121,19 @@ impl TxStatusChange { } } -pub struct SharedState { +pub struct SharedState { tx_status_sender: TxStatusChange, txpool: Arc>>, p2p: Arc, consensus_params: ConsensusParameters, current_height: Arc>, config: Config, + gas_price_provider: Arc, } -impl Clone for SharedState { +impl Clone + for SharedState +{ fn clone(&self) -> Self { Self { tx_status_sender: self.tx_status_sender.clone(), @@ -138,32 +142,35 @@ impl Clone for SharedState { consensus_params: self.consensus_params.clone(), current_height: self.current_height.clone(), config: self.config.clone(), + gas_price_provider: self.gas_price_provider.clone(), } } } -pub struct Task { +pub struct Task { gossiped_tx_stream: BoxStream, committed_block_stream: BoxStream, - shared: SharedState, + tx_pool_shared_state: SharedState, ttl_timer: tokio::time::Interval, } #[async_trait::async_trait] -impl RunnableService for Task +impl RunnableService + for Task where P2P: PeerToPeer, ViewProvider: AtomicView, View: TxPoolDb, + GasPriceProvider: GasPriceProviderConstraint + Send + Sync + Clone, { const NAME: &'static str = "TxPool"; - type SharedData = SharedState; - type Task = Task; + type SharedData = SharedState; + type Task = Task; type TaskParams = (); fn shared_data(&self) -> Self::SharedData { - self.shared.clone() + self.tx_pool_shared_state.clone() } async fn into_task( @@ -177,11 +184,13 @@ where } #[async_trait::async_trait] -impl RunnableTask for Task +impl RunnableTask + for Task where P2P: PeerToPeer, ViewProvider: AtomicView, View: TxPoolDb, + GasPriceProvider: GasPriceProviderConstraint + Send + Sync, { async fn run(&mut self, watcher: &mut StateWatcher) -> anyhow::Result { let should_continue; @@ -194,9 +203,9 @@ where } _ = self.ttl_timer.tick() => { - let removed = self.shared.txpool.lock().prune_old_txs(); + let removed = self.tx_pool_shared_state.txpool.lock().prune_old_txs(); for tx in removed { - self.shared.tx_status_sender.send_squeezed_out(tx.id(), Error::TTLReason); + self.tx_pool_shared_state.tx_status_sender.send_squeezed_out(tx.id(), Error::TTLReason); } should_continue = true @@ -209,11 +218,11 @@ where .entity.header().height(); { - let mut lock = self.shared.txpool.lock(); + let mut lock = self.tx_pool_shared_state.txpool.lock(); lock.block_update( &result.tx_status, ); - *self.shared.current_height.lock() = new_height; + *self.tx_pool_shared_state.current_height.lock() = new_height; } should_continue = true; } else { @@ -223,11 +232,11 @@ where new_transaction = self.gossiped_tx_stream.next() => { if let Some(GossipData { data: Some(tx), message_id, peer_id }) = new_transaction { - let id = tx.id(&self.shared.consensus_params.chain_id); - let current_height = *self.shared.current_height.lock(); + let id = tx.id(&self.tx_pool_shared_state.consensus_params.chain_id); + let current_height = *self.tx_pool_shared_state.current_height.lock(); // verify tx - let checked_tx = check_single_tx(tx, current_height, &self.shared.config).await; + let checked_tx = check_single_tx(tx, current_height, &self.tx_pool_shared_state.config, &self.tx_pool_shared_state.gas_price_provider).await; let acceptance = match checked_tx { Ok(tx) => { @@ -236,8 +245,8 @@ where // insert tx let mut result = tracing::info_span!("Received tx via gossip", %id) .in_scope(|| { - self.shared.txpool.lock().insert( - &self.shared.tx_status_sender, + self.tx_pool_shared_state.txpool.lock().insert( + &self.tx_pool_shared_state.tx_status_sender, txs ) }); @@ -265,7 +274,7 @@ where peer_id, }; - let _ = self.shared.p2p.notify_gossip_transaction_validity(message_info, acceptance); + let _ = self.tx_pool_shared_state.p2p.notify_gossip_transaction_validity(message_info, acceptance); should_continue = true; } else { @@ -289,7 +298,9 @@ where // Instead, `fuel-core` can create a `DatabaseWithTxPool` that aggregates `TxPool` and // storage `Database` together. GraphQL will retrieve data from this `DatabaseWithTxPool` via // `StorageInspect` trait. -impl SharedState { +impl + SharedState +{ pub fn pending_number(&self) -> usize { self.txpool.lock().pending_number() } @@ -354,11 +365,13 @@ impl SharedState { } } -impl SharedState +impl + SharedState where P2P: PeerToPeer, ViewProvider: AtomicView, View: TxPoolDb, + GasPriceProvider: GasPriceProviderConstraint, { #[tracing::instrument(name = "insert_submitted_txn", skip_all)] pub async fn insert( @@ -368,7 +381,13 @@ where // verify txs let current_height = *self.current_height.lock(); - let checked_txs = check_transactions(&txs, current_height, &self.config).await; + let checked_txs = check_transactions( + &txs, + current_height, + &self.config, + &self.gas_price_provider, + ) + .await; let mut valid_txs = vec![]; @@ -444,18 +463,20 @@ pub enum TxStatusMessage { FailedStatus, } -pub fn new_service( +pub fn new_service( config: Config, provider: ViewProvider, importer: Importer, p2p: P2P, current_height: BlockHeight, -) -> Service + gas_price_provider: GasPriceProvider, +) -> Service where Importer: BlockImporter, P2P: PeerToPeer + 'static, ViewProvider: AtomicView, ViewProvider::View: TxPoolDb, + GasPriceProvider: GasPriceProviderConstraint + Send + Sync + Clone, { let p2p = Arc::new(p2p); let gossiped_tx_stream = p2p.gossiped_transaction_events(); @@ -468,7 +489,7 @@ where let task = Task { gossiped_tx_stream, committed_block_stream, - shared: SharedState { + tx_pool_shared_state: SharedState { tx_status_sender: TxStatusChange::new( number_of_active_subscription, // The connection should be closed automatically after the `SqueezedOut` event. @@ -482,6 +503,7 @@ where consensus_params, current_height: Arc::new(ParkingMutex::new(current_height)), config, + gas_price_provider: Arc::new(gas_price_provider), }, ttl_timer, }; diff --git a/crates/services/txpool/src/service/test_helpers.rs b/crates/services/txpool/src/service/test_helpers.rs index 1be18b1f32a..63e2459ba10 100644 --- a/crates/services/txpool/src/service/test_helpers.rs +++ b/crates/services/txpool/src/service/test_helpers.rs @@ -8,6 +8,7 @@ use fuel_core_services::{ stream::BoxStream, Service as ServiceTrait, }; +use fuel_core_txpool::types::GasPrice; use fuel_core_types::{ blockchain::SealedBlock, entities::coins::coin::Coin, @@ -32,17 +33,40 @@ use std::cell::RefCell; type GossipedTransaction = GossipData; pub struct TestContext { - pub(crate) service: Service, + pub(crate) service: Service, mock_db: MockDb, rng: RefCell, } +#[derive(Debug, Clone)] +pub struct MockTxPoolGasPrice { + pub gas_price: Option, +} + +impl MockTxPoolGasPrice { + pub fn new(gas_price: GasPrice) -> Self { + Self { + gas_price: Some(gas_price), + } + } + + pub fn new_none() -> Self { + Self { gas_price: None } + } +} + +impl GasPriceProviderConstraint for MockTxPoolGasPrice { + fn gas_price(&self, _block_height: BlockHeight) -> Option { + self.gas_price + } +} + impl TestContext { pub async fn new() -> Self { TestContextBuilder::new().build_and_start().await } - pub fn service(&self) -> &Service { + pub fn service(&self) -> &Service { &self.service } @@ -188,6 +212,7 @@ impl TestContextBuilder { pub fn build(self) -> TestContext { let rng = RefCell::new(self.rng); + let gas_price = 0; let config = self.config.unwrap_or_default(); let mock_db = self.mock_db; @@ -202,6 +227,7 @@ impl TestContextBuilder { let importer = self .importer .unwrap_or_else(|| MockImporter::with_blocks(vec![])); + let gas_price_provider = MockTxPoolGasPrice::new(gas_price); let service = new_service( config, @@ -209,6 +235,7 @@ impl TestContextBuilder { importer, p2p, Default::default(), + gas_price_provider, ); TestContext { diff --git a/crates/services/txpool/src/txpool.rs b/crates/services/txpool/src/txpool.rs index 2106f64abf9..4e575f41aa2 100644 --- a/crates/services/txpool/src/txpool.rs +++ b/crates/services/txpool/src/txpool.rs @@ -76,6 +76,18 @@ pub struct TxPool { database: ViewProvider, } +/// Trait for getting gas price for the Tx Pool code to look up the gas price for a given block height +pub trait GasPriceProvider { + /// Get gas price for specific block height if it is known + fn gas_price(&self, block_height: BlockHeight) -> Option; +} + +impl GasPriceProvider for Arc { + fn gas_price(&self, block_height: BlockHeight) -> Option { + self.deref().gas_price(block_height) + } +} + impl TxPool { pub fn new(config: Config, database: ViewProvider) -> Self { let max_depth = config.max_depth; @@ -463,25 +475,37 @@ where } } -pub async fn check_transactions( +pub async fn check_transactions( txs: &[Arc], current_height: BlockHeight, config: &Config, -) -> Vec, Error>> { + gas_price_provider: &Provider, +) -> Vec, Error>> +where + Provider: GasPriceProvider, +{ let mut checked_txs = Vec::with_capacity(txs.len()); for tx in txs.iter() { - checked_txs - .push(check_single_tx(tx.deref().clone(), current_height, config).await); + checked_txs.push( + check_single_tx( + tx.deref().clone(), + current_height, + config, + gas_price_provider, + ) + .await, + ); } checked_txs } -pub async fn check_single_tx( +pub async fn check_single_tx( tx: Transaction, current_height: BlockHeight, config: &Config, + gas_price_provider: &GasPrice, ) -> Result, Error> { if tx.is_mint() { return Err(Error::NotSupportedTransactionType) @@ -507,7 +531,11 @@ pub async fn check_single_tx( tx.into_checked_basic(current_height, &config.chain_config.consensus_parameters)? }; - let tx = verify_tx_min_gas_price(tx, config)?; + let gas_price = gas_price_provider + .gas_price(current_height) + .ok_or(Error::GasPriceNotFound(current_height))?; + + let tx = verify_tx_min_gas_price(tx, config, gas_price)?; Ok(tx) } @@ -515,20 +543,20 @@ pub async fn check_single_tx( fn verify_tx_min_gas_price( tx: Checked, config: &Config, + gas_price: GasPrice, ) -> Result, Error> { let tx: CheckedTransaction = tx.into(); - let min_gas_price = config.min_gas_price; let gas_costs = &config.chain_config.consensus_parameters.gas_costs; let fee_parameters = &config.chain_config.consensus_parameters.fee_params; let read = match tx { CheckedTransaction::Script(script) => { - let read = script.into_ready(min_gas_price, gas_costs, fee_parameters)?; - let (_, checked) = read.decompose(); + let ready = script.into_ready(gas_price, gas_costs, fee_parameters)?; + let (_, checked) = ready.decompose(); CheckedTransaction::Script(checked) } CheckedTransaction::Create(create) => { - let read = create.into_ready(min_gas_price, gas_costs, fee_parameters)?; - let (_, checked) = read.decompose(); + let ready = create.into_ready(gas_price, gas_costs, fee_parameters)?; + let (_, checked) = ready.decompose(); CheckedTransaction::Create(checked) } CheckedTransaction::Mint(_) => return Err(Error::MintIsDisallowed), diff --git a/crates/services/txpool/src/txpool/tests.rs b/crates/services/txpool/src/txpool/tests.rs index 8a0cad8c87b..d131ad64aea 100644 --- a/crates/services/txpool/src/txpool/tests.rs +++ b/crates/services/txpool/src/txpool/tests.rs @@ -1,4 +1,5 @@ use crate::{ + service::test_helpers::MockTxPoolGasPrice, test_helpers::{ IntoEstimated, TextContext, @@ -38,6 +39,7 @@ use fuel_core_types::{ }, }; +use crate::types::GasPrice; use fuel_core_types::fuel_tx::Finalizable; use std::{ cmp::Reverse, @@ -50,7 +52,17 @@ use super::check_single_tx; const GAS_LIMIT: Word = 1000; async fn check_unwrap_tx(tx: Transaction, config: &Config) -> Checked { - check_single_tx(tx, Default::default(), config) + let gas_price = 0; + check_unwrap_tx_with_gas_price(tx, config, gas_price).await +} + +async fn check_unwrap_tx_with_gas_price( + tx: Transaction, + config: &Config, + gas_price: GasPrice, +) -> Checked { + let gas_price_provider = MockTxPoolGasPrice::new(gas_price); + check_single_tx(tx, Default::default(), config, &gas_price_provider) .await .expect("Transaction should be checked") } @@ -59,7 +71,17 @@ async fn check_tx( tx: Transaction, config: &Config, ) -> Result, Error> { - check_single_tx(tx, Default::default(), config).await + let gas_price = 0; + check_tx_with_gas_price(tx, config, gas_price).await +} + +async fn check_tx_with_gas_price( + tx: Transaction, + config: &Config, + gas_price: GasPrice, +) -> Result, Error> { + let gas_price_provider = MockTxPoolGasPrice::new(gas_price); + check_single_tx(tx, Default::default(), config, &gas_price_provider).await } #[tokio::test] @@ -870,8 +892,8 @@ async fn find_dependent_tx1_tx2() { #[tokio::test] async fn tx_at_least_min_gas_price_is_insertable() { + let gas_price = 10; let mut context = TextContext::default().config(Config { - min_gas_price: 10, ..Default::default() }); @@ -884,7 +906,7 @@ async fn tx_at_least_min_gas_price_is_insertable() { .finalize_as_transaction(); let mut txpool = context.build(); - let tx = check_unwrap_tx(tx, &txpool.config).await; + let tx = check_unwrap_tx_with_gas_price(tx, &txpool.config, gas_price).await; txpool.insert_single(tx).expect("Tx should be Ok, got Err"); } @@ -899,13 +921,14 @@ async fn tx_below_min_gas_price_is_not_insertable() { .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .finalize_as_transaction(); + let gas_price = 11; - let err = check_tx( + let err = check_tx_with_gas_price( tx, &Config { - min_gas_price: 11, ..Default::default() }, + gas_price, ) .await .expect_err("expected insertion failure"); @@ -989,6 +1012,7 @@ async fn tx_rejected_from_pool_when_gas_price_is_lower_than_another_tx_with_same ) { let mut context = TextContext::default(); let message_amount = 10_000; + let max_fee_limit = 10u64; let gas_price_high = 2u64; let gas_price_low = 1u64; let (message, conflicting_message_input) = @@ -996,14 +1020,14 @@ async fn tx_rejected_from_pool_when_gas_price_is_lower_than_another_tx_with_same let tx_high = TransactionBuilder::script(vec![], vec![]) .tip(gas_price_high) - .max_fee_limit(gas_price_high) + .max_fee_limit(max_fee_limit) .script_gas_limit(GAS_LIMIT) .add_input(conflicting_message_input.clone()) .finalize_as_transaction(); let tx_low = TransactionBuilder::script(vec![], vec![]) .tip(gas_price_low) - .max_fee_limit(gas_price_low) + .max_fee_limit(max_fee_limit) .script_gas_limit(GAS_LIMIT) .add_input(conflicting_message_input) .finalize_as_transaction(); @@ -1013,14 +1037,16 @@ async fn tx_rejected_from_pool_when_gas_price_is_lower_than_another_tx_with_same let mut txpool = context.build(); let tx_high_id = tx_high.id(&ChainId::default()); - let tx_high = check_unwrap_tx(tx_high, &txpool.config).await; + let tx_high = + check_unwrap_tx_with_gas_price(tx_high, &txpool.config, gas_price_high).await; // Insert a tx for the message id with a high gas amount txpool .insert_single(tx_high) .expect("expected successful insertion"); - let tx_low = check_unwrap_tx(tx_low, &txpool.config).await; + let tx_low = + check_unwrap_tx_with_gas_price(tx_low, &txpool.config, gas_price_low).await; // Insert a tx for the message id with a low gas amount // Because the new transaction's id matches an existing transaction, we compare the gas // prices of both the new and existing transactions. Since the existing transaction's gas @@ -1039,6 +1065,7 @@ async fn higher_priced_tx_squeezes_out_lower_priced_tx_with_same_message_id() { let mut context = TextContext::default(); let message_amount = 10_000; let gas_price_high = 2u64; + let max_fee_limit = 10u64; let gas_price_low = 1u64; let (message, conflicting_message_input) = create_message_predicate_from_message(message_amount, 0); @@ -1046,7 +1073,7 @@ async fn higher_priced_tx_squeezes_out_lower_priced_tx_with_same_message_id() { // Insert a tx for the message id with a low gas amount let tx_low = TransactionBuilder::script(vec![], vec![]) .tip(gas_price_low) - .max_fee_limit(gas_price_low) + .max_fee_limit(max_fee_limit) .script_gas_limit(GAS_LIMIT) .add_input(conflicting_message_input.clone()) .finalize_as_transaction(); @@ -1055,7 +1082,8 @@ async fn higher_priced_tx_squeezes_out_lower_priced_tx_with_same_message_id() { let mut txpool = context.build(); let tx_low_id = tx_low.id(&ChainId::default()); - let tx_low = check_unwrap_tx(tx_low, &txpool.config).await; + let tx_low = + check_unwrap_tx_with_gas_price(tx_low, &txpool.config, gas_price_low).await; txpool.insert_single(tx_low).expect("should succeed"); // Insert a tx for the message id with a high gas amount @@ -1064,11 +1092,12 @@ async fn higher_priced_tx_squeezes_out_lower_priced_tx_with_same_message_id() { // price is lower, we accept the new transaction and squeeze out the old transaction. let tx_high = TransactionBuilder::script(vec![], vec![]) .tip(gas_price_high) - .max_fee_limit(gas_price_high) + .max_fee_limit(max_fee_limit) .script_gas_limit(GAS_LIMIT) .add_input(conflicting_message_input) .finalize_as_transaction(); - let tx_high = check_unwrap_tx(tx_high, &txpool.config).await; + let tx_high = + check_unwrap_tx_with_gas_price(tx_high, &txpool.config, gas_price_high).await; let squeezed_out_txs = txpool.insert_single(tx_high).expect("should succeed"); assert_eq!(squeezed_out_txs.removed.len(), 1); diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index c4b39b53e75..573a44967da 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -241,6 +241,8 @@ pub fn from_executor_to_status( #[derive(thiserror::Error, Debug, Clone)] #[non_exhaustive] pub enum Error { + #[error("Gas price not found for block height {0}")] + GasPriceNotFound(BlockHeight), #[error("TxPool required that transaction contains metadata")] NoMetadata, #[error("TxPool doesn't support this type of transaction.")] diff --git a/tests/test-helpers/src/builder.rs b/tests/test-helpers/src/builder.rs index 6c2aa08d2e8..a49b8054323 100644 --- a/tests/test-helpers/src/builder.rs +++ b/tests/test-helpers/src/builder.rs @@ -207,12 +207,12 @@ impl TestSetupBuilder { utxo_validation: self.utxo_validation, txpool: fuel_core_txpool::Config { chain_config: chain_conf.clone(), - min_gas_price: self.min_gas_price, ..fuel_core_txpool::Config::default() }, chain_config: chain_conf, state_reader: StateReader::in_memory(state), block_production: self.trigger, + static_gas_price: self.min_gas_price, ..Config::local_node() }; diff --git a/tests/tests/gas_price.rs b/tests/tests/gas_price.rs index 08a13fc2685..9e763664580 100644 --- a/tests/tests/gas_price.rs +++ b/tests/tests/gas_price.rs @@ -1,32 +1,148 @@ -use fuel_core::service::{ - Config, - FuelService, +#![allow(non_snake_case)] + +use fuel_core::{ + chain_config::{ + CoinConfig, + StateConfig, + StateReader, + }, + service::{ + Config, + FuelService, + }, }; use fuel_core_client::client::{ schema::gas_price::EstimateGasPrice, - types::gas_price::LatestGasPrice, + types::{ + gas_price::LatestGasPrice, + primitives::{ + Address, + AssetId, + }, + }, FuelClient, }; +use fuel_core_types::{ + fuel_crypto::{ + coins_bip32::ecdsa::signature::rand_core::SeedableRng, + SecretKey, + }, + fuel_tx::{ + Finalizable, + Input, + TransactionBuilder, + UtxoId, + }, +}; +use rand::prelude::StdRng; + +async fn setup_service_with_coin( + owner: Address, + amount: u64, + static_gas_price: u64, +) -> (FuelService, UtxoId) { + // setup config + let tx_id = [0u8; 32]; + let output_index = 0; + let coin_config = CoinConfig { + tx_id: tx_id.into(), + output_index, + tx_pointer_block_height: Default::default(), + tx_pointer_tx_idx: 0, + owner, + amount, + asset_id: AssetId::BASE, + }; + let state = StateConfig { + coins: vec![coin_config], + ..Default::default() + }; + let config = Config { + state_reader: StateReader::in_memory(state), + static_gas_price, + ..Config::local_node() + }; + + // setup server & client + let srv = FuelService::new_node(config).await.unwrap(); + + let utxo_id = UtxoId::new(tx_id.into(), output_index); + + (srv, utxo_id) +} #[tokio::test] -async fn latest_gas_price() { - let node_config = Config::local_node(); +async fn latest_gas_price__should_be_static() { + // setup node with a block that has a non-mint transaction + let static_gas_price = 2; + let max_fee_limit = 100; + let mut rng = StdRng::seed_from_u64(1234); + let secret_key: SecretKey = SecretKey::random(&mut rng); + let pk = secret_key.public_key(); + let owner = Input::owner(&pk); + + let (srv, utxo_id) = + setup_service_with_coin(owner, max_fee_limit, static_gas_price).await; + + let client = FuelClient::from(srv.bound_address); + + let tx = TransactionBuilder::script(vec![], vec![]) + .max_fee_limit(1) + .add_unsigned_coin_input( + secret_key, + utxo_id, + max_fee_limit, + AssetId::BASE, + Default::default(), + ) + .finalize() + .into(); + + client.submit_and_await_commit(&tx).await.unwrap(); + + // given + let expected = static_gas_price; + + // when + let LatestGasPrice { gas_price, .. } = client.latest_gas_price().await.unwrap(); + + // then + let actual = gas_price; + assert_eq!(expected, actual) +} + +#[tokio::test] +async fn latest_gas_price__if_no_mint_tx_in_previous_block_gas_price_is_zero() { + // given + let mut node_config = Config::local_node(); + node_config.static_gas_price = 100; let srv = FuelService::new_node(node_config.clone()).await.unwrap(); let client = FuelClient::from(srv.bound_address); + // when let LatestGasPrice { gas_price, .. } = client.latest_gas_price().await.unwrap(); - assert_eq!(gas_price, node_config.txpool.min_gas_price); + + // then + let expected = 0; + let actual = gas_price; + assert_eq!(expected, actual) } #[tokio::test] -async fn estimate_gas_price() { +async fn estimate_gas_price__should_be_static() { + // given let node_config = Config::local_node(); let srv = FuelService::new_node(node_config.clone()).await.unwrap(); let client = FuelClient::from(srv.bound_address); + // when let arbitrary_horizon = 10; let EstimateGasPrice { gas_price } = client.estimate_gas_price(arbitrary_horizon).await.unwrap(); - assert_eq!(u64::from(gas_price), node_config.txpool.min_gas_price); + + // then + let expected = node_config.static_gas_price; + let actual = u64::from(gas_price); + assert_eq!(expected, actual); } diff --git a/tests/tests/node_info.rs b/tests/tests/node_info.rs index 5876be45ade..b5afb7dc911 100644 --- a/tests/tests/node_info.rs +++ b/tests/tests/node_info.rs @@ -16,7 +16,6 @@ async fn node_info() { let NodeInfo { utxo_validation, vm_backtrace, - min_gas_price, max_depth, max_tx, .. @@ -24,7 +23,6 @@ async fn node_info() { assert_eq!(utxo_validation, node_config.utxo_validation); assert_eq!(vm_backtrace, node_config.vm.backtrace); - assert_eq!(min_gas_price, node_config.txpool.min_gas_price); assert_eq!(max_depth, node_config.txpool.max_depth as u64); assert_eq!(max_tx, node_config.txpool.max_tx as u64); } diff --git a/tests/tests/tx/txpool.rs b/tests/tests/tx/txpool.rs index f14c22b52aa..b4856dabb93 100644 --- a/tests/tests/tx/txpool.rs +++ b/tests/tests/tx/txpool.rs @@ -63,7 +63,7 @@ async fn txs_max_script_gas_limit() { .into_iter() .map(|script| Arc::new(fuel_tx::Transaction::from(script))) .collect::>(); - srv.shared.txpool.insert(txs).await; + srv.shared.txpool_shared_state.insert(txs).await; tokio::time::sleep(Duration::from_secs(1)).await;