diff --git a/CHANGELOG.md b/CHANGELOG.md index 69f38263200..af801e7b974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - [1983](https://github.com/FuelLabs/fuel-core/pull/1983): Add adapters for gas price service for accessing database values +### Breaking +- [2025](https://github.com/FuelLabs/fuel-core/pull/2025): Add new V0 algorithm for gas price to services. + This change includes new flags for the CLI: + - "starting-gas-price" - the starting gas price for the gas price algorithm + - "gas-price-change-percent" - the percent change for each gas price update + - "gas-price-threshold-percent" - the threshold percent for determining if the gas price will be increase or decreased + And the following CLI flags are serving a new purpose + - "min-gas-price" - the minimum gas price that the gas price algorithm will return ## [Version 0.31.0] ### Added diff --git a/Cargo.lock b/Cargo.lock index 74af6d9bc55..e518d81f41d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3430,6 +3430,7 @@ dependencies = [ name = "fuel-gas-price-algorithm" version = "0.31.0" dependencies = [ + "proptest", "serde", "thiserror", ] diff --git a/benches/benches/block_target_gas.rs b/benches/benches/block_target_gas.rs index 619a28893a6..3799294319d 100644 --- a/benches/benches/block_target_gas.rs +++ b/benches/benches/block_target_gas.rs @@ -338,7 +338,12 @@ fn service_with_many_contracts( } let service = FuelService::new( - CombinedDatabase::new(database, Default::default(), Default::default()), + CombinedDatabase::new( + database, + Default::default(), + Default::default(), + Default::default(), + ), config.clone(), ) .expect("Unable to start a FuelService"); diff --git a/bin/e2e-test-client/src/test_context.rs b/bin/e2e-test-client/src/test_context.rs index 5a05c7d1d0d..1cb6bcb8b07 100644 --- a/bin/e2e-test-client/src/test_context.rs +++ b/bin/e2e-test-client/src/test_context.rs @@ -191,7 +191,7 @@ impl Wallet { ]; // build transaction - let mut tx = TransactionBuilder::script( + let mut tx_builder = TransactionBuilder::script( script.into_iter().collect(), asset_id .to_bytes() @@ -202,10 +202,11 @@ impl Wallet { .chain(0u64.to_bytes().into_iter()) .collect(), ); - tx.max_fee_limit(BASE_AMOUNT); - tx.script_gas_limit(self.consensus_params.tx_params().max_gas_per_tx() / 10); + tx_builder.max_fee_limit(BASE_AMOUNT); + tx_builder + .script_gas_limit(self.consensus_params.tx_params().max_gas_per_tx() / 10); - tx.add_input(Input::contract( + tx_builder.add_input(Input::contract( Default::default(), Default::default(), Default::default(), @@ -214,7 +215,7 @@ impl Wallet { )); for coin in coins { if let CoinType::Coin(coin) = coin { - tx.add_unsigned_coin_input( + tx_builder.add_unsigned_coin_input( self.secret, coin.utxo_id, coin.amount, @@ -223,20 +224,24 @@ impl Wallet { ); } } - tx.add_output(Output::contract(0, Default::default(), Default::default())); - tx.add_output(Output::Change { + tx_builder.add_output(Output::contract( + 0, + Default::default(), + Default::default(), + )); + tx_builder.add_output(Output::Change { to: self.address, amount: 0, asset_id, }); - tx.add_output(Output::Variable { + tx_builder.add_output(Output::Variable { to: Default::default(), amount: Default::default(), asset_id: Default::default(), }); - tx.with_params(self.consensus_params.clone()); + tx_builder.with_params(self.consensus_params.clone()); - Ok(tx.finalize_as_transaction()) + Ok(tx_builder.finalize_as_transaction()) } /// Transfers coins from this wallet to another diff --git a/bin/e2e-test-client/src/tests/collect_fee.rs b/bin/e2e-test-client/src/tests/collect_fee.rs index c581edcaaae..d7dde4d534e 100644 --- a/bin/e2e-test-client/src/tests/collect_fee.rs +++ b/bin/e2e-test-client/src/tests/collect_fee.rs @@ -32,7 +32,8 @@ pub async fn collect_fee(ctx: &TestContext) -> Result<(), Failed> { .iter() .any(|receipt| matches!(receipt, Receipt::TransferOut { .. })) { - return Err("collect fee hasn't produced `TransferOut` receipt".into()) + let msg = format!("TransferOut receipt not found in receipts: {:?}", receipts); + return Err(msg.into()) } Ok(()) diff --git a/bin/e2e-test-client/src/tests/script.rs b/bin/e2e-test-client/src/tests/script.rs index bc238a154fa..db65ee6cb6f 100644 --- a/bin/e2e-test-client/src/tests/script.rs +++ b/bin/e2e-test-client/src/tests/script.rs @@ -15,13 +15,14 @@ use fuel_core_types::{ UniqueIdentifier, }, fuel_types::{ - canonical::Deserialize, + canonical::{ + Deserialize, + Serialize, + }, Salt, }, services::executor::TransactionExecutionResult, }; - -use fuel_core_types::fuel_types::canonical::Serialize; use libtest_mimic::Failed; use std::{ path::Path, @@ -145,7 +146,6 @@ pub async fn run_contract_large_state(ctx: &TestContext) -> Result<(), Failed> { pub async fn arbitrary_transaction(ctx: &TestContext) -> Result<(), Failed> { const RAW_PATH: &str = "src/tests/test_data/arbitrary_tx.raw"; const JSON_PATH: &str = "src/tests/test_data/arbitrary_tx.json"; - let dry_run_raw = std::fs::read_to_string(RAW_PATH).expect("Should read the raw transaction"); let dry_run_json = diff --git a/bin/e2e-test-client/src/tests/test_data/arbitrary_tx.json b/bin/e2e-test-client/src/tests/test_data/arbitrary_tx.json index 4cbb3d328bd..1b69a5ea67d 100644 --- a/bin/e2e-test-client/src/tests/test_data/arbitrary_tx.json +++ b/bin/e2e-test-client/src/tests/test_data/arbitrary_tx.json @@ -17,7 +17,7 @@ 0, 0, 0, - 100000 + 30000000 ] }, "inputs": [ diff --git a/bin/e2e-test-client/src/tests/test_data/arbitrary_tx.raw b/bin/e2e-test-client/src/tests/test_data/arbitrary_tx.raw index 6b888f55317..c24043bba7b 100644 --- a/bin/e2e-test-client/src/tests/test_data/arbitrary_tx.raw +++ b/bin/e2e-test-client/src/tests/test_data/arbitrary_tx.raw @@ -1 +1 @@ -000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000008000000000000000100000000000000020000000000000001240000000000000000000000000186a000000000000000008b5a47933cbb7ddbc0392a45a124a2a01f17c657d34d4951324003a2f9aff24a000000000000000157cd0f26d30e6e0361742800f0cb39b87d2bd58e052c4389d7e507e39e504a01000000001d34a76ef8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeb709945b9058c3d50f3922bd1b49f92ced2950a9ecaf810aa7829295550cd20000000000002710f8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07000000000000000357cd0f26d30e6e0361742800f0cb39b87d2bd58e052c4389d7e507e39e504a01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file +000000000000000000000000000186a0000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000800000000000000010000000000000002000000000000000124000000000000000000000001c9c38000000000000000008b5a47933cbb7ddbc0392a45a124a2a01f17c657d34d4951324003a2f9aff24a000000000000000157cd0f26d30e6e0361742800f0cb39b87d2bd58e052c4389d7e507e39e504a01000000001d34a76ef8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeb709945b9058c3d50f3922bd1b49f92ced2950a9ecaf810aa7829295550cd20000000000002710f8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07000000000000000357cd0f26d30e6e0361742800f0cb39b87d2bd58e052c4389d7e507e39e504a01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/bin/e2e-test-client/src/tests/test_data/large_state/tx.json b/bin/e2e-test-client/src/tests/test_data/large_state/tx.json index aa472e89986..5557fdecaf3 100644 --- a/bin/e2e-test-client/src/tests/test_data/large_state/tx.json +++ b/bin/e2e-test-client/src/tests/test_data/large_state/tx.json @@ -4138,7 +4138,7 @@ 0, 0, 0, - 100000 + 12390831 ] }, "inputs": [ diff --git a/bin/e2e-test-client/tests/integration_tests.rs b/bin/e2e-test-client/tests/integration_tests.rs index 94b71909c53..f854bbd2030 100644 --- a/bin/e2e-test-client/tests/integration_tests.rs +++ b/bin/e2e-test-client/tests/integration_tests.rs @@ -123,7 +123,7 @@ fn dev_config() -> Config { let reader = reader.with_chain_config(chain_config); let mut config = Config::local_node_with_reader(reader); - config.static_gas_price = 1; + config.starting_gas_price = 1; config.block_producer.coinbase_recipient = Some( ContractId::from_str( "0x7777777777777777777777777777777777777777777777777777777777777777", diff --git a/bin/fuel-core/chainspec/local-testnet/chain_config.json b/bin/fuel-core/chainspec/local-testnet/chain_config.json index c90809be70f..da714fd834d 100644 --- a/bin/fuel-core/chainspec/local-testnet/chain_config.json +++ b/bin/fuel-core/chainspec/local-testnet/chain_config.json @@ -34,7 +34,7 @@ }, "fee_params": { "V1": { - "gas_price_factor": 92, + "gas_price_factor": 92000, "gas_per_byte": 63 } }, diff --git a/bin/fuel-core/chainspec/local-testnet/state_transition_bytecode.wasm b/bin/fuel-core/chainspec/local-testnet/state_transition_bytecode.wasm index f75d97d9e9a..f99b6530a5f 100755 Binary files a/bin/fuel-core/chainspec/local-testnet/state_transition_bytecode.wasm and b/bin/fuel-core/chainspec/local-testnet/state_transition_bytecode.wasm differ diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index db23e597b92..92af7d37aa7 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -165,10 +165,22 @@ pub struct Command { #[arg(long = "native-executor-version", env)] pub native_executor_version: Option, + /// The starting gas price for the network + #[arg(long = "starting-gas-price", default_value = "0", env)] + pub starting_gas_price: u64, + + /// The percentage change in gas price per block + #[arg(long = "gas-price-change-percent", default_value = "0", env)] + pub gas_price_change_percent: u64, + /// The minimum allowed gas price #[arg(long = "min-gas-price", default_value = "0", env)] pub min_gas_price: u64, + /// The percentage threshold for gas price increase + #[arg(long = "gas-price-threshold-percent", default_value = "50", env)] + pub gas_price_threshold_percent: u64, + /// The signing key used when producing blocks. /// Setting via the `CONSENSUS_KEY_SECRET` ENV var is preferred. #[arg(long = "consensus-key", env)] @@ -245,7 +257,10 @@ impl Command { debug, utxo_validation, native_executor_version, + starting_gas_price, + gas_price_change_percent, min_gas_price, + gas_price_threshold_percent, consensus_key, poa_trigger, coinbase_recipient, @@ -408,7 +423,10 @@ impl Command { coinbase_recipient, metrics, }, - static_gas_price: min_gas_price, + starting_gas_price, + gas_price_change_percent, + min_gas_price, + gas_price_threshold_percent, block_importer, #[cfg(feature = "relayer")] relayer: relayer_cfg, diff --git a/crates/client/src/client/types/gas_price.rs b/crates/client/src/client/types/gas_price.rs index bc1f63905d0..a13a0d61d6b 100644 --- a/crates/client/src/client/types/gas_price.rs +++ b/crates/client/src/client/types/gas_price.rs @@ -1,6 +1,7 @@ use crate::client::schema; use fuel_core_types::fuel_types::BlockHeight; +#[derive(Debug, Copy, Clone)] pub struct LatestGasPrice { pub gas_price: u64, pub block_height: BlockHeight, diff --git a/crates/fuel-core/src/combined_database.rs b/crates/fuel-core/src/combined_database.rs index f30fffa2575..3d0aab73d2b 100644 --- a/crates/fuel-core/src/combined_database.rs +++ b/crates/fuel-core/src/combined_database.rs @@ -1,6 +1,9 @@ +#[cfg(feature = "rocksdb")] +use crate::state::historical_rocksdb::StateRewindPolicy; use crate::{ database::{ database_description::{ + gas_price::GasPriceDatabase, off_chain::OffChain, on_chain::OnChain, relayer::Relayer, @@ -28,9 +31,6 @@ use fuel_core_storage::tables::{ use fuel_core_storage::Result as StorageResult; use std::path::PathBuf; -#[cfg(feature = "rocksdb")] -use crate::state::historical_rocksdb::StateRewindPolicy; - #[derive(Clone, Debug, Eq, PartialEq)] pub struct CombinedDatabaseConfig { pub database_path: PathBuf, @@ -46,6 +46,7 @@ pub struct CombinedDatabase { on_chain: Database, off_chain: Database, relayer: Database, + gas_price: Database, } impl CombinedDatabase { @@ -53,11 +54,13 @@ impl CombinedDatabase { on_chain: Database, off_chain: Database, relayer: Database, + gas_price: Database, ) -> Self { Self { on_chain, off_chain, relayer, + gas_price, } } @@ -66,6 +69,7 @@ impl CombinedDatabase { crate::state::rocks_db::RocksDb::::prune(path)?; crate::state::rocks_db::RocksDb::::prune(path)?; crate::state::rocks_db::RocksDb::::prune(path)?; + crate::state::rocks_db::RocksDb::::prune(path)?; Ok(()) } @@ -80,10 +84,12 @@ impl CombinedDatabase { let off_chain = Database::open_rocksdb(path, capacity, state_rewind_policy)?; let relayer = Database::open_rocksdb(path, capacity, StateRewindPolicy::NoRewind)?; + let gas_price = Database::open_rocksdb(path, capacity, state_rewind_policy)?; Ok(Self { on_chain, off_chain, relayer, + gas_price, }) } @@ -124,6 +130,7 @@ impl CombinedDatabase { Database::in_memory(), Database::in_memory(), Database::in_memory(), + Database::in_memory(), ) } @@ -161,6 +168,15 @@ impl CombinedDatabase { &mut self.relayer } + pub fn gas_price(&self) -> &Database { + &self.gas_price + } + + #[cfg(any(feature = "test-helpers", test))] + pub fn gas_price_mut(&mut self) -> &mut Database { + &mut self.gas_price + } + #[cfg(feature = "test-helpers")] pub fn read_state_config(&self) -> StorageResult { use fuel_core_chain_config::AddTable; diff --git a/crates/fuel-core/src/service.rs b/crates/fuel-core/src/service.rs index 1cc0491fe52..72fa390f07c 100644 --- a/crates/fuel-core/src/service.rs +++ b/crates/fuel-core/src/service.rs @@ -118,8 +118,12 @@ impl FuelService { database: Database, config: Config, ) -> anyhow::Result { - let combined_database = - CombinedDatabase::new(database, Default::default(), Default::default()); + let combined_database = CombinedDatabase::new( + database, + Default::default(), + Default::default(), + Default::default(), + ); Self::from_combined_database(combined_database, config).await } 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 f1f762cb85c..4319dfd5218 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 @@ -4,7 +4,7 @@ use fuel_core_gas_price_service::{ SharedGasPriceAlgo, }; use fuel_core_producer::block_producer::gas_price::GasPriceProvider as ProducerGasPriceProvider; -use fuel_core_txpool::ports::GasPriceProvider as TxPoolGasPricProvider; +use fuel_core_txpool::ports::GasPriceProvider as TxPoolGasPriceProvider; use fuel_core_types::{ fuel_types::BlockHeight, services::txpool::Result as TxPoolResult, @@ -53,12 +53,8 @@ impl FuelGasPriceProvider where A: GasPriceAlgorithm + Send + Sync, { - async fn next_gas_price(&self, block_bytes: u64) -> u64 { - self.algorithm.next_gas_price(block_bytes).await - } - - async fn last_gas_price(&self) -> u64 { - self.algorithm.last_gas_price().await + async fn next_gas_price(&self) -> u64 { + self.algorithm.next_gas_price().await } } @@ -67,18 +63,18 @@ impl ProducerGasPriceProvider for FuelGasPriceProvider where A: GasPriceAlgorithm + Send + Sync, { - async fn next_gas_price(&self, block_bytes: u64) -> anyhow::Result { - Ok(self.next_gas_price(block_bytes).await) + async fn next_gas_price(&self) -> anyhow::Result { + Ok(self.next_gas_price().await) } } #[async_trait::async_trait] -impl TxPoolGasPricProvider for FuelGasPriceProvider +impl TxPoolGasPriceProvider for FuelGasPriceProvider where A: GasPriceAlgorithm + Send + Sync, { - async fn last_gas_price(&self) -> TxPoolResult { - Ok(self.last_gas_price().await) + async fn next_gas_price(&self) -> TxPoolResult { + Ok(self.next_gas_price().await) } } diff --git a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests.rs b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests.rs index 2e428f5e3ff..7ae74e62d68 100644 --- a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests.rs +++ b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests.rs @@ -11,35 +11,6 @@ mod tx_pool_gas_price_tests; #[cfg(test)] mod graph_ql_gas_price_estimate_tests; -#[derive(Debug, Clone, Copy)] -pub struct TestGasPriceAlgorithm { - last: u64, - multiply: u64, -} - -impl Default for TestGasPriceAlgorithm { - fn default() -> Self { - Self { - last: 100, - multiply: 2, - } - } -} - -impl GasPriceAlgorithm for TestGasPriceAlgorithm { - fn last_gas_price(&self) -> u64 { - self.last - } - - fn next_gas_price(&self, block_bytes: u64) -> u64 { - self.multiply.saturating_mul(block_bytes) - } - - fn worst_case_gas_price(&self, _block_height: BlockHeight) -> u64 { - self.multiply.saturating_mul(10_000_000) // Arbitrary fake bytes - } -} - fn build_provider(algorithm: A) -> FuelGasPriceProvider where A: Send + Sync, diff --git a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/graph_ql_gas_price_estimate_tests.rs b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/graph_ql_gas_price_estimate_tests.rs index 64c350f0fc5..5ffb079c462 100644 --- a/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/graph_ql_gas_price_estimate_tests.rs +++ b/crates/fuel-core/src/service/adapters/fuel_gas_price_provider/tests/graph_ql_gas_price_estimate_tests.rs @@ -1,11 +1,13 @@ use super::*; +use fuel_core_gas_price_service::static_updater::StaticAlgorithm; #[tokio::test] async fn estimate_gas_price__happy_path() { // given let next_height = 432.into(); - let algo = TestGasPriceAlgorithm::default(); - let gas_price_provider = build_provider(algo); + let price = 33; + let algo = StaticAlgorithm::new(price); + let gas_price_provider = build_provider(algo.clone()); // when let expected_price = algo.worst_case_gas_price(next_height); 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 55d1d0310b2..9c1c9d3aad3 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,19 +1,19 @@ -use crate::service::adapters::fuel_gas_price_provider::tests::{ - build_provider, - TestGasPriceAlgorithm, +use crate::service::adapters::fuel_gas_price_provider::tests::build_provider; +use fuel_core_gas_price_service::{ + static_updater::StaticAlgorithm, + GasPriceAlgorithm, }; -use fuel_core_gas_price_service::GasPriceAlgorithm; #[tokio::test] async fn gas_price__if_requested_block_height_is_latest_return_gas_price() { // given - let algo = TestGasPriceAlgorithm::default(); - let gas_price_provider = build_provider(algo); - let bytes = 10; + let price = 33; + let algo = StaticAlgorithm::new(price); + let gas_price_provider = build_provider(algo.clone()); // when - let expected_price = algo.next_gas_price(bytes); - let actual_price = gas_price_provider.next_gas_price(bytes).await; + let expected_price = algo.next_gas_price(); + let actual_price = gas_price_provider.next_gas_price().await; // then assert_eq!(expected_price, actual_price); 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 b69c5e62916..9c1c9d3aad3 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,18 +1,19 @@ -use crate::service::adapters::fuel_gas_price_provider::tests::{ - build_provider, - TestGasPriceAlgorithm, +use crate::service::adapters::fuel_gas_price_provider::tests::build_provider; +use fuel_core_gas_price_service::{ + static_updater::StaticAlgorithm, + GasPriceAlgorithm, }; -use fuel_core_gas_price_service::GasPriceAlgorithm; #[tokio::test] async fn gas_price__if_requested_block_height_is_latest_return_gas_price() { // given - let algo = TestGasPriceAlgorithm::default(); - let gas_price_provider = build_provider(algo); + let price = 33; + let algo = StaticAlgorithm::new(price); + let gas_price_provider = build_provider(algo.clone()); // when - let expected_price = algo.last_gas_price(); - let actual_price = gas_price_provider.last_gas_price().await; + let expected_price = algo.next_gas_price(); + let actual_price = gas_price_provider.next_gas_price().await; // then assert_eq!(expected_price, actual_price); diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index 397035434f5..3490027406f 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -231,7 +231,7 @@ impl fuel_core_producer::ports::BlockProducerDatabase for OnChainIterableKeyValu #[async_trait::async_trait] impl GasPriceProvider for StaticGasPrice { - async fn next_gas_price(&self, _block_bytes: u64) -> anyhow::Result { + async fn next_gas_price(&self) -> anyhow::Result { Ok(self.gas_price) } } diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index b89bcde8720..4ee44db6c28 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -144,7 +144,7 @@ impl fuel_core_txpool::ports::TxPoolDb for OnChainIterableKeyValueView { #[async_trait::async_trait] impl GasPriceProvider for StaticGasPrice { - async fn last_gas_price(&self) -> TxPoolResult { + async fn next_gas_price(&self) -> TxPoolResult { Ok(self.gas_price) } } diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index 3e855768725..1c375a4af41 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -1,37 +1,35 @@ -use clap::ValueEnum; -use fuel_core_chain_config::SnapshotReader; -use fuel_core_types::{ - blockchain::{ - header::StateTransitionBytecodeVersion, - primitives::SecretKeyWrapper, - }, - secrecy::Secret, -}; use std::time::Duration; + +use clap::ValueEnum; use strum_macros::{ Display, EnumString, EnumVariantNames, }; -#[cfg(feature = "p2p")] -use fuel_core_p2p::config::{ - Config as P2PConfig, - NotInitialized, -}; - -#[cfg(feature = "relayer")] -use fuel_core_relayer::Config as RelayerConfig; - +use fuel_core_chain_config::SnapshotReader; #[cfg(feature = "test-helpers")] use fuel_core_chain_config::{ ChainConfig, StateConfig, }; - pub use fuel_core_consensus_module::RelayerConsensusConfig; pub use fuel_core_importer; +#[cfg(feature = "p2p")] +use fuel_core_p2p::config::{ + Config as P2PConfig, + NotInitialized, +}; pub use fuel_core_poa::Trigger; +#[cfg(feature = "relayer")] +use fuel_core_relayer::Config as RelayerConfig; +use fuel_core_types::{ + blockchain::{ + header::StateTransitionBytecodeVersion, + primitives::SecretKeyWrapper, + }, + secrecy::Secret, +}; use crate::{ combined_database::CombinedDatabaseConfig, @@ -56,7 +54,10 @@ 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 starting_gas_price: u64, + pub gas_price_change_percent: u64, + pub min_gas_price: u64, + pub gas_price_threshold_percent: u64, pub block_importer: fuel_core_importer::Config, #[cfg(feature = "relayer")] pub relayer: Option, @@ -109,7 +110,6 @@ impl Config { ); let utxo_validation = false; - let min_gas_price = 0; let combined_db_config = CombinedDatabaseConfig { // Set the cache for tests = 10MB @@ -122,6 +122,10 @@ impl Config { #[cfg(feature = "rocksdb")] state_rewind_policy: Default::default(), }; + let starting_gas_price = 0; + let gas_price_change_percent = 0; + let min_gas_price = 0; + let gas_price_threshold_percent = 50; Self { graphql_config: GraphQLConfig { @@ -152,7 +156,10 @@ impl Config { block_producer: fuel_core_producer::Config { ..Default::default() }, - static_gas_price: min_gas_price, + starting_gas_price, + gas_price_change_percent, + min_gas_price, + gas_price_threshold_percent, block_importer, #[cfg(feature = "relayer")] relayer: None, diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 7fde20bc62f..0e39a872d12 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -3,6 +3,8 @@ use super::{ adapters::P2PAdapter, genesis::create_genesis_block, }; +#[cfg(feature = "relayer")] +use crate::relayer::Config as RelayerConfig; use crate::{ combined_database::CombinedDatabase, database::Database, @@ -12,6 +14,9 @@ use crate::{ service::{ adapters::{ consensus_parameters_provider, + fuel_gas_price_provider::FuelGasPriceProvider, + graphql_api::GraphQLBlockImporter, + import_result_provider::ImportResultProvider, BlockImporterAdapter, BlockProducerAdapter, ConsensusParametersProvider, @@ -27,24 +32,22 @@ use crate::{ SubServices, }, }; -use fuel_core_poa::Trigger; -use fuel_core_storage::transactional::AtomicView; -use std::sync::Arc; -use tokio::sync::Mutex; - -#[cfg(feature = "relayer")] -use crate::relayer::Config as RelayerConfig; -use crate::service::adapters::{ - fuel_gas_price_provider::FuelGasPriceProvider, - graphql_api::GraphQLBlockImporter, - import_result_provider::ImportResultProvider, +use fuel_core_gas_price_service::fuel_gas_price_updater::{ + fuel_core_storage_adapter::FuelL2BlockSource, + Algorithm, + FuelGasPriceUpdater, + UpdaterMetadata, + V0Metadata, }; -use fuel_core_gas_price_service::static_updater::{ - StaticAlgorithm, - StaticAlgorithmUpdater, +use fuel_core_poa::Trigger; +use fuel_core_storage::{ + structured_storage::StructuredStorage, + transactional::AtomicView, }; #[cfg(feature = "relayer")] use fuel_core_types::blockchain::primitives::DaBlockHeight; +use std::sync::Arc; +use tokio::sync::Mutex; pub type PoAService = fuel_core_poa::Service; @@ -53,7 +56,7 @@ pub type P2PService = fuel_core_p2p::service::Service; pub type TxPoolSharedState = fuel_core_txpool::service::SharedState< P2PAdapter, Database, - FuelGasPriceProvider, + FuelGasPriceProvider, ConsensusParametersProvider, SharedMemoryPool, >; @@ -61,7 +64,7 @@ pub type BlockProducerService = fuel_core_producer::block_producer::Producer< Database, TxPoolAdapter, ExecutorAdapter, - FuelGasPriceProvider, + FuelGasPriceProvider, ConsensusParametersProvider, >; @@ -173,7 +176,21 @@ pub fn init_sub_services( #[cfg(not(feature = "p2p"))] let p2p_adapter = P2PAdapter::new(); - let update_algo = StaticAlgorithmUpdater::new(config.static_gas_price); + let updater_metadata = UpdaterMetadata::V0(V0Metadata { + new_exec_price: config.starting_gas_price, + min_exec_gas_price: config.min_gas_price, + exec_gas_price_change_percent: config.gas_price_change_percent, + l2_block_height: last_height.into(), + l2_block_fullness_threshold_percent: config.gas_price_threshold_percent, + }); + let genesis_block_height = *genesis_block.header().height(); + let settings = consensus_parameters_provider.clone(); + let block_stream = importer_adapter.events_shared_result(); + let l2_block_source = + FuelL2BlockSource::new(genesis_block_height, settings, block_stream); + let metadata_storage = StructuredStorage::new(database.gas_price().clone()); + let update_algo = + FuelGasPriceUpdater::init(updater_metadata, l2_block_source, metadata_storage)?; let gas_price_service = fuel_core_gas_price_service::new_service(last_height, update_algo)?; let next_algo = gas_price_service.shared.clone(); diff --git a/crates/fuel-gas-price-algorithm/Cargo.toml b/crates/fuel-gas-price-algorithm/Cargo.toml index a5823897801..54b5a1c2cfd 100644 --- a/crates/fuel-gas-price-algorithm/Cargo.toml +++ b/crates/fuel-gas-price-algorithm/Cargo.toml @@ -15,5 +15,6 @@ name = "fuel_gas_price_algorithm" path = "src/lib.rs" [dependencies] +proptest = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } diff --git a/crates/fuel-gas-price-algorithm/src/v0.rs b/crates/fuel-gas-price-algorithm/src/v0.rs index 731bd20b8c8..bb3fb92147a 100644 --- a/crates/fuel-gas-price-algorithm/src/v0.rs +++ b/crates/fuel-gas-price-algorithm/src/v0.rs @@ -16,12 +16,34 @@ pub enum Error { pub struct AlgorithmV0 { /// The gas price for to cover the execution of the next block new_exec_price: u64, + /// The block height of the next L2 block + for_height: u32, + /// The change percentage per block + percentage: u64, } impl AlgorithmV0 { pub fn calculate(&self) -> u64 { self.new_exec_price } + + #[allow(clippy::cast_possible_truncation)] + pub fn worst_case(&self, height: u32) -> u64 { + let price = self.new_exec_price as f64; + let blocks = height.saturating_sub(self.for_height) as f64; + let percentage = self.percentage as f64; + let percentage_as_decimal = percentage / 100.0; + let multiple = (1.0f64 + percentage_as_decimal).powf(blocks); + let mut approx = price * multiple; + // account for rounding errors and take a slightly higher value + const ARB_CUTOFF: f64 = 16948547188989277.0; + if approx > ARB_CUTOFF { + const ARB_ADDITION: f64 = 2000.0; + approx += ARB_ADDITION; + } + // `f64` over `u64::MAX` are cast to `u64::MAX` + approx.ceil() as u64 + } } /// The state of the algorithm used to update the gas price algorithm for each block @@ -85,7 +107,7 @@ impl AlgorithmUpdaterV0 { .unwrap_or(self.l2_block_fullness_threshold_percent); match fullness_percent.cmp(&self.l2_block_fullness_threshold_percent) { - std::cmp::Ordering::Greater => { + std::cmp::Ordering::Greater | std::cmp::Ordering::Equal => { let change_amount = self.change_amount(exec_gas_price); exec_gas_price = exec_gas_price.saturating_add(change_amount); } @@ -93,7 +115,6 @@ impl AlgorithmUpdaterV0 { let change_amount = self.change_amount(exec_gas_price); exec_gas_price = exec_gas_price.saturating_sub(change_amount); } - std::cmp::Ordering::Equal => {} } self.new_exec_price = max(self.min_exec_gas_price, exec_gas_price); } @@ -107,6 +128,8 @@ impl AlgorithmUpdaterV0 { pub fn algorithm(&self) -> AlgorithmV0 { AlgorithmV0 { new_exec_price: self.new_exec_price, + for_height: self.l2_block_height, + percentage: self.exec_gas_price_change_percent, } } } diff --git a/crates/fuel-gas-price-algorithm/src/v0/tests.rs b/crates/fuel-gas-price-algorithm/src/v0/tests.rs index 2bb5ad63cf6..49c5bf49ac7 100644 --- a/crates/fuel-gas-price-algorithm/src/v0/tests.rs +++ b/crates/fuel-gas-price-algorithm/src/v0/tests.rs @@ -5,7 +5,7 @@ use super::AlgorithmUpdaterV0; #[cfg(test)] -mod algorithm_v1_tests; +mod algorithm_v0_tests; #[cfg(test)] mod update_l2_block_data_tests; diff --git a/crates/fuel-gas-price-algorithm/src/v0/tests/algorithm_v0_tests.rs b/crates/fuel-gas-price-algorithm/src/v0/tests/algorithm_v0_tests.rs new file mode 100644 index 00000000000..b972acc0225 --- /dev/null +++ b/crates/fuel-gas-price-algorithm/src/v0/tests/algorithm_v0_tests.rs @@ -0,0 +1,105 @@ +use crate::v0::AlgorithmV0; +use proptest::prelude::*; + +#[test] +fn calculate__gives_static_value() { + // given + let value = 100; + let algorithm = AlgorithmV0 { + new_exec_price: value, + for_height: 0, + percentage: 0, + }; + + // when + let actual = algorithm.calculate(); + + // then + let expected = value; + assert_eq!(expected, actual); +} + +fn _worst_case__correctly_calculates_value( + price: u64, + starting_height: u32, + block_horizon: u32, + percentage: u64, +) { + // given + let algorithm = AlgorithmV0 { + new_exec_price: price, + for_height: starting_height, + percentage, + }; + + // when + let target_height = starting_height + block_horizon; + let actual = algorithm.worst_case(target_height); + + // then + let mut expected = price; + for _ in 0..block_horizon { + let change_amount = expected.saturating_mul(percentage).saturating_div(100); + expected = expected.saturating_add(change_amount); + } + dbg!(actual, expected); + assert!(actual >= expected); +} + +proptest! { + #[test] + fn worst_case__correctly_calculates_value( + price: u64, + starting_height: u32, + block_horizon in 0..10_000u32, + percentage: u64 + ) { + _worst_case__correctly_calculates_value(price, starting_height, block_horizon, percentage); + } +} + +proptest! { + #[test] + fn worst_case__never_overflows( + price: u64, + starting_height: u32, + block_horizon: u32, + percentage: u64 + ) { + // given + let algorithm = AlgorithmV0 { + new_exec_price: price, + for_height: starting_height, + percentage, + }; + + // when + let target_height = starting_height.saturating_add(block_horizon); + let _ = algorithm.worst_case(target_height); + + // then + // doesn't panic with an overflow + } +} + +#[test] +fn worst_case__same_block_gives_new_exec_price() { + // given + let new_exec_price = 1000; + let for_height = 10; + let percentage = 10; + let algorithm = AlgorithmV0 { + new_exec_price, + for_height, + percentage, + }; + + // when + let delta = 0; + let target_height = for_height + delta; + let actual = algorithm.worst_case(target_height); + + // then + let expected = new_exec_price; + assert_eq!(expected, actual); +} diff --git a/crates/fuel-gas-price-algorithm/src/v0/tests/algorithm_v1_tests.rs b/crates/fuel-gas-price-algorithm/src/v0/tests/algorithm_v1_tests.rs deleted file mode 100644 index db467f84b34..00000000000 --- a/crates/fuel-gas-price-algorithm/src/v0/tests/algorithm_v1_tests.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::v0::AlgorithmV0; - -#[test] -fn calculate__gives_static_value() { - // given - let value = 100; - let algorithm = AlgorithmV0 { - new_exec_price: value, - }; - - // when - let actual = algorithm.calculate(); - - // then - let expected = value; - assert_eq!(expected, actual); -} diff --git a/crates/fuel-gas-price-algorithm/src/v0/tests/update_l2_block_data_tests.rs b/crates/fuel-gas-price-algorithm/src/v0/tests/update_l2_block_data_tests.rs index 1073d04a0ab..5596575b5f6 100644 --- a/crates/fuel-gas-price-algorithm/src/v0/tests/update_l2_block_data_tests.rs +++ b/crates/fuel-gas-price-algorithm/src/v0/tests/update_l2_block_data_tests.rs @@ -53,7 +53,7 @@ fn update_l2_block_data__skipped_block_height_throws_error() { } #[test] -fn update_l2_block_data__even_threshold_will_not_change_exec_gas_price() { +fn update_l2_block_data__even_threshold_will_increase_exec_gas_price() { // given let starting_gas_price = 100; let unused_percent = 11; @@ -72,7 +72,7 @@ fn update_l2_block_data__even_threshold_will_not_change_exec_gas_price() { .unwrap(); // then - let expected = starting_gas_price; + let expected = starting_gas_price + starting_gas_price * unused_percent / 100; let actual = updater.new_exec_price; assert_eq!(actual, expected); } 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 index 2f78e7685c3..e32f3ddcae7 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater.rs +++ b/crates/services/gas_price_service/src/fuel_gas_price_updater.rs @@ -1,13 +1,18 @@ -use crate::UpdateAlgorithm; -use fuel_core_types::fuel_types::BlockHeight; -use fuel_gas_price_algorithm::v0::{ - AlgorithmUpdaterV0, - AlgorithmV0, +use crate::{ + GasPriceAlgorithm, + UpdateAlgorithm, }; -pub use fuel_gas_price_algorithm::v1::{ - AlgorithmUpdaterV1, - AlgorithmV1, - RecordedBlock, +use fuel_core_types::fuel_types::BlockHeight; +pub use fuel_gas_price_algorithm::{ + v0::{ + AlgorithmUpdaterV0, + AlgorithmV0, + }, + v1::{ + AlgorithmUpdaterV1, + AlgorithmV1, + RecordedBlock, + }, }; use std::num::NonZeroU64; @@ -17,14 +22,33 @@ mod tests; pub mod fuel_core_storage_adapter; pub struct FuelGasPriceUpdater { - inner: AlgorithmUpdaterV0, + inner: AlgorithmUpdater, l2_block_source: L2, metadata_storage: Metadata, } +#[derive(Debug, Clone, PartialEq)] +pub enum AlgorithmUpdater { + V0(AlgorithmUpdaterV0), +} + +impl AlgorithmUpdater { + pub fn algorithm(&self) -> Algorithm { + match self { + AlgorithmUpdater::V0(v0) => Algorithm::V0(v0.algorithm()), + } + } + + pub fn l2_block_height(&self) -> BlockHeight { + match self { + AlgorithmUpdater::V0(v0) => v0.l2_block_height.into(), + } + } +} + impl FuelGasPriceUpdater { pub fn new( - inner: AlgorithmUpdaterV0, + inner: AlgorithmUpdater, l2_block_source: L2, metadata_storage: Metadata, ) -> Self { @@ -62,13 +86,18 @@ pub type Result = std::result::Result; // Info required about the l2 block for the gas price algorithm #[derive(Debug, Clone, PartialEq)] -pub struct BlockInfo { - // Block height - pub height: u32, - // Gas used in the block - pub gas_used: u64, - // Total gas capacity of the block - pub block_gas_capacity: u64, +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 { @@ -88,9 +117,8 @@ impl UpdaterMetadata { } } -impl TryFrom for AlgorithmUpdaterV0 { - type Error = anyhow::Error; - fn try_from(metadata: UpdaterMetadata) -> Result { +impl From for AlgorithmUpdater { + fn from(metadata: UpdaterMetadata) -> Self { match metadata { UpdaterMetadata::V0(v1_no_da) => { let V0Metadata { @@ -107,7 +135,7 @@ impl TryFrom for AlgorithmUpdaterV0 { l2_block_height, l2_block_fullness_threshold_percent, }; - Ok(updater) + AlgorithmUpdater::V0(updater) } } } @@ -130,16 +158,21 @@ pub struct V0Metadata { pub l2_block_fullness_threshold_percent: u64, } -impl From for UpdaterMetadata { - fn from(v1: AlgorithmUpdaterV0) -> Self { - let v0 = V0Metadata { - new_exec_price: v1.new_exec_price, - min_exec_gas_price: v1.min_exec_gas_price, - exec_gas_price_change_percent: v1.exec_gas_price_change_percent, - l2_block_height: v1.l2_block_height, - l2_block_fullness_threshold_percent: v1.l2_block_fullness_threshold_percent, - }; - UpdaterMetadata::V0(v0) +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) + } + } } } @@ -162,8 +195,7 @@ where let inner = metadata_storage .get_metadata(&target_block_height)? .unwrap_or(init_metadata) - .try_into() - .map_err(Error::CouldNotInitUpdater)?; + .into(); let updater = Self { inner, l2_block_source, @@ -179,34 +211,56 @@ where L2: L2BlockSource, Metadata: MetadataStorage + Send + Sync, { - type Algorithm = AlgorithmV0; + type Algorithm = Algorithm; fn start(&self, _for_block: BlockHeight) -> Self::Algorithm { self.inner.algorithm() } async fn next(&mut self) -> anyhow::Result { - tokio::select! { - l2_block = self.l2_block_source.get_l2_block(self.inner.l2_block_height.into()) => { - tracing::info!("Received L2 block: {:?}", l2_block); - let l2_block = l2_block?; - let BlockInfo { - height, - gas_used, - block_gas_capacity, - } = l2_block; + 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?; + let AlgorithmUpdater::V0(ref mut updater) = &mut self.inner; + match l2_block { + BlockInfo::GenesisBlock => { + // do nothing + } + BlockInfo::Block { + height, + gas_used, + block_gas_capacity, + } => { let capacity = NonZeroU64::new(block_gas_capacity).ok_or_else(|| { anyhow::anyhow!("Block gas capacity must be non-zero") })?; - self.inner.update_l2_block_data( - height, - gas_used, - capacity, - )?; + updater.update_l2_block_data(height, gas_used, capacity)?; self.metadata_storage .set_metadata(self.inner.clone().into())?; - Ok(self.inner.algorithm()) } } + Ok(self.inner.algorithm()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Algorithm { + V0(AlgorithmV0), +} + +impl GasPriceAlgorithm for Algorithm { + fn next_gas_price(&self) -> u64 { + match self { + Algorithm::V0(v0) => v0.calculate(), + } + } + + fn worst_case_gas_price(&self, height: BlockHeight) -> u64 { + match self { + Algorithm::V0(v0) => v0.worst_case(height.into()), + } } } 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/fuel_gas_price_updater/fuel_core_storage_adapter.rs index 018d08d0ef3..21ac241be9e 100644 --- a/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter.rs +++ b/crates/services/gas_price_service/src/fuel_gas_price_updater/fuel_core_storage_adapter.rs @@ -87,10 +87,25 @@ where } 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, @@ -110,9 +125,9 @@ fn get_block_info( ) -> GasPriceResult { let (fee, gas_price) = mint_values(block)?; let height = *block.header().height(); - let calculated_used_gas = block_used_gas(height, fee, gas_price, gas_price_factor)?; - let used_gas = min(calculated_used_gas, block_gas_limit); - let info = BlockInfo { + let used_gas = + block_used_gas(height, fee, gas_price, gas_price_factor, block_gas_limit)?; + let info = BlockInfo::Block { height: (*block.header().height()).into(), gas_used: used_gas, block_gas_capacity: block_gas_limit, @@ -136,6 +151,7 @@ fn block_used_gas( fee: u64, gas_price: u64, gas_price_factor: u64, + max_used_gas: u64, ) -> GasPriceResult { let scaled_fee = fee.checked_mul(gas_price_factor) @@ -145,12 +161,10 @@ fn block_used_gas( "Failed to scale fee by gas price factor, overflow" ), })?; - scaled_fee - .checked_div(gas_price) - .ok_or(GasPriceError::CouldNotFetchL2Block { - block_height, - source_error: anyhow!("Failed to calculate gas used, division by zero"), - }) + // If gas price is zero, assume max used gas + let approximate = scaled_fee.checked_div(gas_price).unwrap_or(max_used_gas); + let used_gas = min(approximate, max_used_gas); + Ok(used_gas) } #[async_trait::async_trait] @@ -172,12 +186,21 @@ where .sealed_block .entity; - let param_version = block.header().consensus_parameters_version; + 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) + 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/fuel_gas_price_updater/fuel_core_storage_adapter/l2_source_tests.rs index db4e7b238f8..06bc49b342e 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/fuel_gas_price_updater/fuel_core_storage_adapter/l2_source_tests.rs @@ -61,10 +61,12 @@ impl GasPriceSettingsProvider for FakeSettings { } fn l2_source( + genesis_block_height: BlockHeight, gas_price_settings: T, committed_block_stream: BoxStream, ) -> FuelL2BlockSource { FuelL2BlockSource { + genesis_block_height, gas_price_settings, committed_block_stream, } @@ -98,8 +100,10 @@ fn block_to_import_result(block: Block) -> SharedImportResult { async fn get_l2_block__gets_expected_value() { // given let params = params(); - let (block, _mint) = build_block(¶ms.chain_id()); + 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; let expected = get_block_info(&block, gas_price_factor, block_gas_limit).unwrap(); @@ -109,7 +113,7 @@ async fn get_l2_block__gets_expected_value() { vec![import_result]; let block_stream = tokio_stream::iter(blocks).into_boxed(); - let mut source = l2_source(settings, block_stream); + let mut source = l2_source(genesis_block_height, settings, block_stream); // when let actual = source.get_l2_block(block_height).await.unwrap(); @@ -123,15 +127,16 @@ async fn get_l2_block__waits_for_block() { // given let block_height = 1u32.into(); let params = params(); - let (block, _mint) = build_block(¶ms.chain_id()); + let (block, _mint) = build_block(¶ms.chain_id(), block_height); + let genesis_block_height = 0u32.into(); let gas_price_factor = 100; let block_gas_limit = 1000; let settings = FakeSettings::new(gas_price_factor, block_gas_limit); - let (_sender, receiver) = tokio::sync::mpsc::channel(1); + let (sender, receiver) = tokio::sync::mpsc::channel(1); let stream = ReceiverStream::new(receiver); let block_stream = Box::pin(stream); - let mut source = l2_source(settings, block_stream); + let mut source = l2_source(genesis_block_height, settings, block_stream); // when let mut fut_l2_block = source.get_l2_block(block_height); @@ -147,7 +152,7 @@ async fn get_l2_block__waits_for_block() { } let import_result = block_to_import_result(block.clone()); - _sender.send(import_result).await.unwrap(); + sender.send(import_result).await.unwrap(); // then let actual = fut_l2_block.await.unwrap(); @@ -155,7 +160,7 @@ async fn get_l2_block__waits_for_block() { assert_eq!(expected, actual); } -fn build_block(chain_id: &ChainId) -> (Block, Transaction) { +fn build_block(chain_id: &ChainId, height: BlockHeight) -> (Block, Transaction) { let mut inner_mint = Mint::default(); *inner_mint.gas_price_mut() = 500; *inner_mint.mint_amount_mut() = 1000; @@ -164,6 +169,7 @@ fn build_block(chain_id: &ChainId) -> (Block, Transaction) { let tx_id = tx.id(chain_id); let mut block = CompressedBlock::default(); block.transactions_mut().push(tx_id); + block.header_mut().consensus_mut().height = height; let new = block.uncompress(vec![tx.clone()]); (new, tx) } @@ -172,9 +178,10 @@ fn build_block(chain_id: &ChainId) -> (Block, Transaction) { async fn get_l2_block__calculates_gas_used_correctly() { // given let chain_id = ChainId::default(); - let (block, mint) = build_block(&chain_id); let block_height = 1u32.into(); + let (block, mint) = build_block(&chain_id, block_height); + let genesis_block_height = 0u32.into(); let gas_price_factor = 100; let block_gas_limit = 1000; let settings = FakeSettings::new(gas_price_factor, block_gas_limit); @@ -184,13 +191,18 @@ async fn get_l2_block__calculates_gas_used_correctly() { vec![import_result]; let block_stream = tokio_stream::iter(blocks).into_boxed(); - let mut source = l2_source(settings, block_stream); + let mut source = l2_source(genesis_block_height, settings, block_stream); // when let result = source.get_l2_block(block_height).await.unwrap(); // then - let actual = result.gas_used; + let BlockInfo::Block { + gas_used: actual, .. + } = result + else { + panic!("Expected non-genesis"); + }; let (fee, gas_price) = if let Transaction::Mint(inner_mint) = &mint { let fee = inner_mint.mint_amount(); let gas_price = inner_mint.gas_price(); @@ -207,9 +219,10 @@ async fn get_l2_block__calculates_gas_used_correctly() { async fn get_l2_block__calculates_block_gas_capacity_correctly() { // given let chain_id = ChainId::default(); - let (block, _mint) = build_block(&chain_id); let block_height = 1u32.into(); + let (block, _mint) = build_block(&chain_id, block_height); + let genesis_block_height = 0u32.into(); let gas_price_factor = 100; let block_gas_limit = 1000; let settings = FakeSettings::new(gas_price_factor, block_gas_limit); @@ -219,13 +232,45 @@ async fn get_l2_block__calculates_block_gas_capacity_correctly() { vec![import_result]; let block_stream = tokio_stream::iter(blocks).into_boxed(); - let mut source = l2_source(settings, block_stream); + let mut source = l2_source(genesis_block_height, settings, block_stream); // when let result = source.get_l2_block(block_height).await.unwrap(); // then - let actual = result.block_gas_capacity; + let BlockInfo::Block { + block_gas_capacity: actual, + .. + } = result + else { + panic!("Expected non-genesis"); + }; let expected = block_gas_limit; assert_eq!(expected, actual); } + +#[tokio::test] +async fn get_l2_block__if_block_precedes_genesis_block_throw_an_error() { + // given + let chain_id = ChainId::default(); + let block_height = 1u32.into(); + let (block, _mint) = build_block(&chain_id, block_height); + + let genesis_block_height = 2u32.into(); + let gas_price_factor = 100; + let block_gas_limit = 1000; + let settings = FakeSettings::new(gas_price_factor, block_gas_limit); + + let import_result = block_to_import_result(block.clone()); + let blocks: Vec + Send + Sync>> = + vec![import_result]; + let block_stream = tokio_stream::iter(blocks).into_boxed(); + + let mut source = l2_source(genesis_block_height, settings, block_stream); + + // when + let error = source.get_l2_block(block_height).await.unwrap_err(); + + // then + assert!(matches!(error, GasPriceError::CouldNotFetchL2Block { .. })); +} 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/fuel_gas_price_updater/fuel_core_storage_adapter/metadata_tests.rs index 7f5a4c6958d..79e6ed748ce 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/fuel_gas_price_updater/fuel_core_storage_adapter/metadata_tests.rs @@ -1,5 +1,9 @@ #![allow(non_snake_case)] +use crate::fuel_gas_price_updater::{ + fuel_core_storage_adapter::storage::GasPriceColumn, + AlgorithmUpdater, +}; use fuel_core_storage::{ structured_storage::test::InMemoryStorage, transactional::{ @@ -10,8 +14,6 @@ use fuel_core_storage::{ }; use fuel_gas_price_algorithm::v0::AlgorithmUpdaterV0; -use crate::fuel_gas_price_updater::fuel_core_storage_adapter::storage::GasPriceColumn; - use super::*; fn arb_metadata() -> UpdaterMetadata { @@ -20,14 +22,14 @@ fn arb_metadata() -> UpdaterMetadata { } fn arb_metadata_with_l2_height(l2_height: BlockHeight) -> UpdaterMetadata { - AlgorithmUpdaterV0 { + let inner = AlgorithmUpdaterV0 { new_exec_price: 100, min_exec_gas_price: 12, exec_gas_price_change_percent: 2, l2_block_height: l2_height.into(), l2_block_fullness_threshold_percent: 0, - } - .into() + }; + AlgorithmUpdater::V0(inner).into() } fn database() -> StorageTransaction> { 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 index db903b88eb1..cea31928f37 100644 --- 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 @@ -78,7 +78,7 @@ fn different_arb_metadata() -> UpdaterMetadata { #[tokio::test] async fn next__fetches_l2_block() { // given - let l2_block = BlockInfo { + let l2_block = BlockInfo::Block { height: 1, gas_used: 60, block_gas_capacity: 100, @@ -122,7 +122,7 @@ async fn init__if_exists_already_reload() { .unwrap(); // then - let expected: AlgorithmUpdaterV0 = metadata.try_into().unwrap(); + let expected: AlgorithmUpdater = metadata.into(); let actual = updater.inner; assert_eq!(expected, actual); } @@ -140,16 +140,15 @@ async fn init__if_it_does_not_exist_create_with_provided_values() { .unwrap(); // then - let expected: AlgorithmUpdaterV0 = metadata.try_into().unwrap(); + let expected: AlgorithmUpdater = metadata.into(); let actual = updater.inner; assert_eq!(expected, actual); } #[tokio::test] async fn next__new_l2_block_saves_old_metadata() { - let _ = tracing_subscriber::fmt::try_init(); // given - let l2_block = BlockInfo { + let l2_block = BlockInfo::Block { height: 1, gas_used: 60, block_gas_capacity: 100, diff --git a/crates/services/gas_price_service/src/lib.rs b/crates/services/gas_price_service/src/lib.rs index 6fc322474e2..09c80c3b98a 100644 --- a/crates/services/gas_price_service/src/lib.rs +++ b/crates/services/gas_price_service/src/lib.rs @@ -72,8 +72,7 @@ pub trait UpdateAlgorithm { } pub trait GasPriceAlgorithm { - fn last_gas_price(&self) -> u64; - fn next_gas_price(&self, block_bytes: u64) -> u64; + fn next_gas_price(&self) -> u64; fn worst_case_gas_price(&self, block_height: BlockHeight) -> u64; } @@ -113,12 +112,8 @@ impl SharedGasPriceAlgo where A: GasPriceAlgorithm + Send + Sync, { - pub async fn next_gas_price(&self, block_bytes: u64) -> u64 { - self.0.read().await.next_gas_price(block_bytes) - } - - pub async fn last_gas_price(&self) -> u64 { - self.0.read().await.last_gas_price() + 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 { @@ -203,14 +198,10 @@ mod tests { } impl GasPriceAlgorithm for TestAlgorithm { - fn last_gas_price(&self) -> u64 { + fn next_gas_price(&self) -> u64 { self.price } - fn next_gas_price(&self, block_bytes: u64) -> u64 { - self.price + block_bytes - } - fn worst_case_gas_price(&self, _block_height: BlockHeight) -> u64 { self.price } @@ -257,7 +248,7 @@ mod tests { tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; // then - let actual_price = read_algo.last_gas_price().await; + let actual_price = read_algo.next_gas_price().await; assert_eq!(expected_price, actual_price); } } diff --git a/crates/services/gas_price_service/src/static_updater.rs b/crates/services/gas_price_service/src/static_updater.rs index e728ef23177..c2f45a3d60b 100644 --- a/crates/services/gas_price_service/src/static_updater.rs +++ b/crates/services/gas_price_service/src/static_updater.rs @@ -15,6 +15,7 @@ impl StaticAlgorithmUpdater { } } +#[derive(Clone, Debug)] pub struct StaticAlgorithm { price: u64, } @@ -30,11 +31,7 @@ impl StaticAlgorithm { } impl GasPriceAlgorithm for StaticAlgorithm { - fn last_gas_price(&self) -> u64 { - self.price() - } - - fn next_gas_price(&self, _block_bytes: u64) -> u64 { + fn next_gas_price(&self) -> u64 { self.price() } diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index 476b8685fc9..e86120473a2 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -121,7 +121,7 @@ where let header = self.new_header(height, block_time).await?; - let gas_price = self.calculate_gas_price(&header).await?; + let gas_price = self.calculate_gas_price().await?; let component = Components { header_to_produce: header, @@ -143,20 +143,9 @@ where Ok(result) } - async fn calculate_gas_price( - &self, - header: &PartialBlockHeader, - ) -> anyhow::Result { - let consensus_params = self - .consensus_parameters_provider - .consensus_params_at_version(&header.consensus_parameters_version)?; - let gas_per_bytes = consensus_params.fee_params().gas_per_byte(); - let max_gas_per_block = consensus_params.block_gas_limit(); - let max_block_bytes = max_gas_per_block.checked_div(gas_per_bytes).ok_or(anyhow!( - "Unable to calculate max block bytes from gas limit: {max_gas_per_block}, gas per byte: {gas_per_bytes}" - ))?; + async fn calculate_gas_price(&self) -> anyhow::Result { self.gas_price_provider - .next_gas_price(max_block_bytes) + .next_gas_price() .await .map_err(|e| anyhow!("No gas price found: {e:?}")) } @@ -239,7 +228,7 @@ where let gas_price = if let Some(inner) = gas_price { inner } else { - self.calculate_gas_price(&header).await? + self.calculate_gas_price().await? }; // The dry run execution should use the state of the blockchain based on the diff --git a/crates/services/producer/src/block_producer/gas_price.rs b/crates/services/producer/src/block_producer/gas_price.rs index f450240f439..12e08cfa2c7 100644 --- a/crates/services/producer/src/block_producer/gas_price.rs +++ b/crates/services/producer/src/block_producer/gas_price.rs @@ -5,7 +5,7 @@ use std::sync::Arc; /// Interface for retrieving the gas price for a block pub trait GasPriceProvider { /// The gas price for all transactions in the block. - async fn next_gas_price(&self, block_bytes: u64) -> anyhow::Result; + async fn next_gas_price(&self) -> anyhow::Result; } /// Interface for retrieving the consensus parameters. diff --git a/crates/services/producer/src/block_producer/tests.rs b/crates/services/producer/src/block_producer/tests.rs index 265f1e35d46..fb2c7859f24 100644 --- a/crates/services/producer/src/block_producer/tests.rs +++ b/crates/services/producer/src/block_producer/tests.rs @@ -63,7 +63,7 @@ impl MockProducerGasPrice { #[async_trait::async_trait] impl GasPriceProvider for MockProducerGasPrice { - async fn next_gas_price(&self, _block_bytes: u64) -> anyhow::Result { + async fn next_gas_price(&self) -> anyhow::Result { self.gas_price .ok_or_else(|| anyhow::anyhow!("Gas price not provided")) } diff --git a/crates/services/txpool/src/ports.rs b/crates/services/txpool/src/ports.rs index 2cdeec3aca1..c013703460a 100644 --- a/crates/services/txpool/src/ports.rs +++ b/crates/services/txpool/src/ports.rs @@ -66,7 +66,7 @@ pub trait TxPoolDb: Send + Sync { /// Trait for getting gas price for the Tx Pool code to look up the gas price for a given block height pub trait GasPriceProvider { /// Calculate gas price for the next block with a given size `block_bytes`. - async fn last_gas_price(&self) -> TxPoolResult; + async fn next_gas_price(&self) -> TxPoolResult; } /// Trait for getting VM memory. @@ -92,7 +92,7 @@ impl GasPriceProvider for Arc where T: GasPriceProvider + Send + Sync, { - async fn last_gas_price(&self) -> TxPoolResult { - self.deref().last_gas_price().await + async fn next_gas_price(&self) -> TxPoolResult { + self.deref().next_gas_price().await } } diff --git a/crates/services/txpool/src/service.rs b/crates/services/txpool/src/service.rs index 05f103a4236..abe7fe52293 100644 --- a/crates/services/txpool/src/service.rs +++ b/crates/services/txpool/src/service.rs @@ -1,23 +1,16 @@ -use crate::{ - ports::{ - BlockImporter, - ConsensusParametersProvider, - GasPriceProvider as GasPriceProviderConstraint, - MemoryPool, - PeerToPeer, - TxPoolDb, - }, - transaction_selector::select_transactions, - txpool::{ - check_single_tx, - check_transactions, - }, - Config, - Error as TxPoolError, - TxInfo, - TxPool, +use std::{ + sync::Arc, + time::Duration, }; + use anyhow::anyhow; +use parking_lot::Mutex as ParkingMutex; +use tokio::{ + sync::broadcast, + time::MissedTickBehavior, +}; +use tokio_stream::StreamExt; + use fuel_core_services::{ stream::BoxStream, RunnableService, @@ -53,18 +46,28 @@ use fuel_core_types::{ }, tai64::Tai64, }; -use parking_lot::Mutex as ParkingMutex; -use std::{ - sync::Arc, - time::Duration, -}; -use tokio::{ - sync::broadcast, - time::MissedTickBehavior, -}; -use tokio_stream::StreamExt; use update_sender::UpdateSender; +use crate::{ + ports::{ + BlockImporter, + ConsensusParametersProvider, + GasPriceProvider as GasPriceProviderConstraint, + MemoryPool, + PeerToPeer, + TxPoolDb, + }, + transaction_selector::select_transactions, + txpool::{ + check_single_tx, + check_transactions, + }, + Config, + Error as TxPoolError, + TxInfo, + TxPool, +}; + use self::update_sender::{ MpscChannel, TxStatusStream, diff --git a/crates/services/txpool/src/service/test_helpers.rs b/crates/services/txpool/src/service/test_helpers.rs index 2c6643a4008..477794b3955 100644 --- a/crates/services/txpool/src/service/test_helpers.rs +++ b/crates/services/txpool/src/service/test_helpers.rs @@ -79,7 +79,7 @@ impl MockTxPoolGasPrice { #[async_trait::async_trait] impl GasPriceProviderConstraint for MockTxPoolGasPrice { - async fn last_gas_price(&self) -> TxPoolResult { + async fn next_gas_price(&self) -> TxPoolResult { self.gas_price.ok_or(TxPoolError::GasPriceNotFound( "Gas price not found".to_string(), )) diff --git a/crates/services/txpool/src/txpool.rs b/crates/services/txpool/src/txpool.rs index 0afaa8216dd..9bed5bb4645 100644 --- a/crates/services/txpool/src/txpool.rs +++ b/crates/services/txpool/src/txpool.rs @@ -535,7 +535,7 @@ where tx.into_checked_basic(current_height, consensus_params)? }; - let gas_price = gas_price_provider.last_gas_price().await?; + let gas_price = gas_price_provider.next_gas_price().await?; let tx = verify_tx_min_gas_price(tx, consensus_params, gas_price)?; diff --git a/crates/types/src/blockchain/header.rs b/crates/types/src/blockchain/header.rs index 861cda181da..9da0daf1ffc 100644 --- a/crates/types/src/blockchain/header.rs +++ b/crates/types/src/blockchain/header.rs @@ -75,8 +75,7 @@ impl BlockHeader { } } - /// Mutable getter for consensus portion of header - fn consensus_mut(&mut self) -> &mut ConsensusHeader { + fn _consensus_mut(&mut self) -> &mut ConsensusHeader { match self { BlockHeader::V1(v1) => &mut v1.consensus, } @@ -85,6 +84,10 @@ impl BlockHeader { #[cfg(feature = "test-helpers")] impl BlockHeader { + /// Mutable getter for consensus portion of header + pub fn consensus_mut(&mut self) -> &mut ConsensusHeader { + self._consensus_mut() + } /// Set the entire consensus header pub fn set_consensus_header( &mut self, @@ -353,7 +356,7 @@ impl BlockHeader { /// Re-generate the header metadata. pub fn recalculate_metadata(&mut self) { let application_hash = self.application().hash(); - self.consensus_mut().generated.application_hash = application_hash; + self._consensus_mut().generated.application_hash = application_hash; let id = self.hash(); match self { BlockHeader::V1(v1) => { diff --git a/tests/test-helpers/src/builder.rs b/tests/test-helpers/src/builder.rs index 074412fe539..9e1b12f06d4 100644 --- a/tests/test-helpers/src/builder.rs +++ b/tests/test-helpers/src/builder.rs @@ -90,7 +90,7 @@ pub struct TestSetupBuilder { pub rng: StdRng, pub contracts: HashMap, pub initial_coins: Vec, - pub min_gas_price: u64, + pub starting_gas_price: u64, pub gas_limit: Option, pub starting_block: Option, pub utxo_validation: bool, @@ -226,7 +226,7 @@ impl TestSetupBuilder { utxo_validation: self.utxo_validation, txpool: fuel_core_txpool::Config::default(), block_production: self.trigger, - static_gas_price: self.min_gas_price, + starting_gas_price: self.starting_gas_price, ..Config::local_node_with_configs(chain_conf, state) }; @@ -247,7 +247,7 @@ impl Default for TestSetupBuilder { rng: StdRng::seed_from_u64(2322u64), contracts: Default::default(), initial_coins: vec![], - min_gas_price: 0, + starting_gas_price: 0, gas_limit: None, starting_block: None, utxo_validation: true, diff --git a/tests/test-helpers/src/fuel_core_driver.rs b/tests/test-helpers/src/fuel_core_driver.rs index 298b7e969d5..8d6b7d4efed 100644 --- a/tests/test-helpers/src/fuel_core_driver.rs +++ b/tests/test-helpers/src/fuel_core_driver.rs @@ -20,6 +20,33 @@ impl FuelCoreDriver { Self::spawn_with_directory(tempdir()?, extra_args).await } + pub async fn spawn_feeless(extra_args: &[&str]) -> anyhow::Result { + let mut args = vec![ + "--starting-gas-price", + "0", + "--gas-price-change-percent", + "0", + ]; + args.extend(extra_args); + Self::spawn_with_directory(tempdir()?, &args).await + } + + pub async fn spawn_feeless_with_directory( + db_dir: TempDir, + extra_args: &[&str], + ) -> anyhow::Result { + let mut args = vec![ + "--starting-gas-price", + "0", + "--gas-price-change-percent", + "0", + "--min-gas-price", + "0", + ]; + args.extend(extra_args); + Self::spawn_with_directory(db_dir, &args).await + } + pub async fn spawn_with_directory( db_dir: TempDir, extra_args: &[&str], diff --git a/tests/tests/chain.rs b/tests/tests/chain.rs index eef3b5bd79e..c5c62b8f600 100644 --- a/tests/tests/chain.rs +++ b/tests/tests/chain.rs @@ -79,7 +79,6 @@ async fn network_operates_with_non_zero_chain_id() { let node_config = Config { debug: true, utxo_validation: true, - static_gas_price: 1, ..Config::local_node_with_configs(chain_config, state_config) }; @@ -117,6 +116,7 @@ async fn network_operates_with_non_zero_base_asset_id() { let mut rng = rand::rngs::StdRng::seed_from_u64(0xBAADF00D); let secret = SecretKey::random(&mut rng); let amount = 10000; + let starting_gas_price = 1; let owner = Input::owner(&secret.public_key()); let utxo_id = UtxoId::new([1; 32].into(), 0); @@ -141,7 +141,7 @@ async fn network_operates_with_non_zero_base_asset_id() { let node_config = Config { debug: true, utxo_validation: true, - static_gas_price: 1, + starting_gas_price, ..Config::local_node_with_configs(chain_config, state_config) }; diff --git a/tests/tests/gas_price.rs b/tests/tests/gas_price.rs index 73b74b05fe2..8e5ff6f94f5 100644 --- a/tests/tests/gas_price.rs +++ b/tests/tests/gas_price.rs @@ -4,28 +4,22 @@ use crate::helpers::{ TestContext, TestSetupBuilder, }; - use fuel_core::{ chain_config::{ - CoinConfig, + ChainConfig, StateConfig, }, + database::Database, service::{ Config, FuelService, }, }; use fuel_core_client::client::{ - schema::gas_price::EstimateGasPrice, - types::{ - gas_price::LatestGasPrice, - primitives::{ - Address, - AssetId, - }, - }, + types::gas_price::LatestGasPrice, FuelClient, }; +use fuel_core_poa::Trigger; use fuel_core_types::{ fuel_asm::*, fuel_crypto::{ @@ -33,127 +27,253 @@ use fuel_core_types::{ SecretKey, }, fuel_tx::{ + consensus_parameters::ConsensusParametersV1, + ConsensusParameters, Finalizable, - Input, + Transaction, TransactionBuilder, - UtxoId, }, services::executor::TransactionExecutionResult, }; -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 { - static_gas_price, - ..Config::local_node_with_state_config(state) - }; - - // setup server & client - let srv = FuelService::new_node(config).await.unwrap(); - - let utxo_id = UtxoId::new(tx_id.into(), output_index); +use rand::Rng; +use std::{ + iter::repeat, + time::Duration, +}; +use test_helpers::fuel_core_driver::FuelCoreDriver; - (srv, utxo_id) +fn tx_for_gas_limit(max_fee_limit: Word) -> Transaction { + TransactionBuilder::script(vec![], vec![]) + .max_fee_limit(max_fee_limit) + .add_random_fee_input() + .finalize() + .into() } -#[tokio::test] -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) +fn arb_large_tx( + max_fee_limit: Word, + rng: &mut R, +) -> Transaction { + let mut script: Vec<_> = repeat(op::noop()).take(10_000).collect(); + script.push(op::ret(RegId::ONE)); + let script_bytes = script.iter().flat_map(|op| op.to_bytes()).collect(); + let mut builder = TransactionBuilder::script(script_bytes, vec![]); + let asset_id = *builder.get_params().base_asset_id(); + builder + .max_fee_limit(max_fee_limit) + .script_gas_limit(22430) .add_unsigned_coin_input( - secret_key, - utxo_id, - max_fee_limit, - AssetId::BASE, + SecretKey::random(rng), + rng.gen(), + u32::MAX as u64, + asset_id, Default::default(), ) .finalize() - .into(); - - client.submit_and_await_commit(&tx).await.unwrap(); + .into() +} +#[tokio::test] +async fn latest_gas_price__if_no_mint_tx_in_previous_block_gas_price_is_zero() { // given - let expected = static_gas_price; + 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 LatestGasPrice { gas_price, .. } = client.latest_gas_price().await.unwrap(); // then + let expected = 0; 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() { +async fn latest_gas_price__for_single_block_should_be_starting_gas_price() { // 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 mut config = Config::local_node(); + let starting_gas_price = 982; + config.starting_gas_price = starting_gas_price; + let srv = FuelService::from_database(Database::default(), config.clone()) + .await + .unwrap(); let client = FuelClient::from(srv.bound_address); // when + let tx = tx_for_gas_limit(1); + let _ = client.submit_and_await_commit(&tx).await.unwrap(); let LatestGasPrice { gas_price, .. } = client.latest_gas_price().await.unwrap(); // then - let expected = 0; + let expected = starting_gas_price; let actual = gas_price; assert_eq!(expected, actual) } #[tokio::test] -async fn estimate_gas_price__should_be_static() { +async fn produce_block__raises_gas_price() { // given - let node_config = Config::local_node(); + let block_gas_limit = 3_000_000; + let chain_config = ChainConfig { + consensus_parameters: ConsensusParameters::V1(ConsensusParametersV1 { + block_gas_limit, + ..Default::default() + }), + ..ChainConfig::local_testnet() + }; + let mut node_config = + Config::local_node_with_configs(chain_config, StateConfig::local_testnet()); + let starting_gas_price = 1_000_000_000; + let percent = 10; + let threshold = 50; + node_config.block_producer.coinbase_recipient = Some([5; 32].into()); + node_config.starting_gas_price = starting_gas_price; + node_config.gas_price_change_percent = percent; + node_config.gas_price_threshold_percent = threshold; + node_config.block_production = Trigger::Never; + + let srv = FuelService::new_node(node_config.clone()).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + let mut rng = rand::rngs::StdRng::seed_from_u64(2322u64); + + // when + let arb_tx_count = 10; + for i in 0..arb_tx_count { + let tx = arb_large_tx(189028 + i as Word, &mut rng); + let _status = client.submit(&tx).await.unwrap(); + } + // starting gas price + let _ = client.produce_blocks(1, None).await.unwrap(); + // updated gas price + let _ = client.produce_blocks(1, None).await.unwrap(); + + // then + let change = starting_gas_price * percent / 100; + let expected = starting_gas_price + change; + let latest = client.latest_gas_price().await.unwrap(); + let actual = latest.gas_price; + assert_eq!(expected, actual); +} + +#[tokio::test] +async fn produce_block__lowers_gas_price() { + // given + let block_gas_limit = 3_000_000; + let chain_config = ChainConfig { + consensus_parameters: ConsensusParameters::V1(ConsensusParametersV1 { + block_gas_limit, + ..Default::default() + }), + ..ChainConfig::local_testnet() + }; + let mut node_config = + Config::local_node_with_configs(chain_config, StateConfig::local_testnet()); + let starting_gas_price = 1_000_000_000; + let percent = 10; + let threshold = 50; + node_config.block_producer.coinbase_recipient = Some([5; 32].into()); + node_config.starting_gas_price = starting_gas_price; + node_config.gas_price_change_percent = percent; + node_config.gas_price_threshold_percent = threshold; + node_config.block_production = Trigger::Never; + + let srv = FuelService::new_node(node_config.clone()).await.unwrap(); + let client = FuelClient::from(srv.bound_address); + let mut rng = rand::rngs::StdRng::seed_from_u64(2322u64); + + // when + let arb_tx_count = 5; + for i in 0..arb_tx_count { + let tx = arb_large_tx(189028 + i as Word, &mut rng); + let _status = client.submit(&tx).await.unwrap(); + } + // starting gas price + let _ = client.produce_blocks(1, None).await.unwrap(); + // updated gas price + let _ = client.produce_blocks(1, None).await.unwrap(); + + // then + let change = starting_gas_price * percent / 100; + let expected = starting_gas_price - change; + let latest = client.latest_gas_price().await.unwrap(); + let actual = latest.gas_price; + assert_eq!(expected, actual); +} + +#[tokio::test] +async fn estimate_gas_price__is_greater_than_actual_price_at_desired_height() { + // given + let mut node_config = Config::local_node(); + let starting_gas_price = 1000; + let percent = 10; + node_config.starting_gas_price = starting_gas_price; + node_config.gas_price_change_percent = percent; + // Always increase + node_config.gas_price_threshold_percent = 0; + 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(); + let estimate = client.estimate_gas_price(arbitrary_horizon).await.unwrap(); + let _ = client.produce_blocks(1, None).await.unwrap(); + for _ in 0..arbitrary_horizon { + let _ = client.produce_blocks(1, None).await.unwrap(); + tokio::time::sleep(Duration::from_millis(10)).await; + } // then - let expected = node_config.static_gas_price; - let actual = u64::from(gas_price); + let latest = client.latest_gas_price().await.unwrap(); + let real = latest.gas_price; + let estimated = u64::from(estimate.gas_price); + assert!(estimated >= real); +} + +#[tokio::test] +async fn latest_gas_price__if_node_restarts_gets_latest_value() { + // given + let args = vec![ + "--debug", + "--poa-instant", + "true", + "--starting-gas-price", + "1000", + "--gas-price-change-percent", + "10", + "--gas-price-threshold-percent", + "0", + ]; + let driver = FuelCoreDriver::spawn(&args).await.unwrap(); + let starting = driver.node.shared.config.starting_gas_price; + let arb_blocks_to_produce = 10; + for _ in 0..arb_blocks_to_produce { + driver.client.produce_blocks(1, None).await.unwrap(); + tokio::time::sleep(Duration::from_millis(10)).await; + } + let latest_gas_price = driver.client.latest_gas_price().await.unwrap(); + let LatestGasPrice { gas_price, .. } = latest_gas_price; + let expected = gas_price; + assert_ne!(expected, starting); + + // when + let temp_dir = driver.kill().await; + let recovered_driver = FuelCoreDriver::spawn_with_directory(temp_dir, &args) + .await + .unwrap(); + + // then + let new_latest_gas_price = recovered_driver.client.latest_gas_price().await.unwrap(); + let LatestGasPrice { gas_price, .. } = new_latest_gas_price; + let actual = gas_price; assert_eq!(expected, actual); } #[tokio::test] -async fn dry_run_opt_with_zero_gas_price() { +async fn dry_run_opt__zero_gas_price_equal_to_none_gas_price() { + // given let tx = TransactionBuilder::script( op::ret(RegId::ONE).to_bytes().into_iter().collect(), vec![], @@ -164,13 +284,14 @@ async fn dry_run_opt_with_zero_gas_price() { .finalize_as_transaction(); let mut test_builder = TestSetupBuilder::new(2322u64); - test_builder.min_gas_price = 1; + test_builder.starting_gas_price = 1; let TestContext { client, srv: _dont_drop, .. } = test_builder.finalize().await; + // when let TransactionExecutionResult::Success { total_fee, total_gas, @@ -201,6 +322,7 @@ async fn dry_run_opt_with_zero_gas_price() { panic!("dry run should have succeeded"); }; + // then assert_ne!(total_fee, total_fee_zero_gas_price); assert_eq!(total_fee_zero_gas_price, 0); diff --git a/tests/tests/regenesis.rs b/tests/tests/regenesis.rs index 9480ad62bb3..438f4e8ca9a 100644 --- a/tests/tests/regenesis.rs +++ b/tests/tests/regenesis.rs @@ -78,8 +78,8 @@ async fn take_snapshot(db_dir: &TempDir, snapshot_dir: &TempDir) -> anyhow::Resu async fn test_regenesis_old_blocks_are_preserved() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); - let core = FuelCoreDriver::spawn(&["--debug", "--poa-instant", "true"]).await?; - + let core = + FuelCoreDriver::spawn_feeless(&["--debug", "--poa-instant", "true"]).await?; // Add some blocks produce_block_with_tx(&mut rng, &core.client).await; produce_block_with_tx(&mut rng, &core.client).await; @@ -108,7 +108,7 @@ async fn test_regenesis_old_blocks_are_preserved() -> anyhow::Result<()> { // ------------------------- Start a node with the first regenesis ------------------------- // Start a new node with the snapshot - let core = FuelCoreDriver::spawn(&[ + let core = FuelCoreDriver::spawn_feeless(&[ "--debug", "--poa-instant", "true", @@ -145,7 +145,7 @@ async fn test_regenesis_old_blocks_are_preserved() -> anyhow::Result<()> { // ------------------------- Start a node with the second regenesis ------------------------- // Make sure the old blocks persisted through the second regenesis - let core = FuelCoreDriver::spawn(&[ + let core = FuelCoreDriver::spawn_feeless(&[ "--debug", "--poa-instant", "true", @@ -209,7 +209,7 @@ async fn test_regenesis_spent_messages_are_preserved() -> anyhow::Result<()> { .write_state_config(state_config, &ChainConfig::local_testnet()) .unwrap(); - let core = FuelCoreDriver::spawn(&[ + let core = FuelCoreDriver::spawn_feeless(&[ "--debug", "--poa-instant", "true", @@ -260,7 +260,7 @@ async fn test_regenesis_spent_messages_are_preserved() -> anyhow::Result<()> { // ------------------------- Start a node with the regenesis ------------------------- // Start a new node with the snapshot - let core = FuelCoreDriver::spawn(&[ + let core = FuelCoreDriver::spawn_feeless(&[ "--debug", "--poa-instant", "true", @@ -282,7 +282,8 @@ async fn test_regenesis_spent_messages_are_preserved() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn test_regenesis_processed_transactions_are_preserved() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); - let core = FuelCoreDriver::spawn(&["--debug", "--poa-instant", "true"]).await?; + let core = + FuelCoreDriver::spawn_feeless(&["--debug", "--poa-instant", "true"]).await?; // Add some blocks let secret = SecretKey::random(&mut rng); @@ -323,7 +324,7 @@ async fn test_regenesis_processed_transactions_are_preserved() -> anyhow::Result // ------------------------- Start a node with the regenesis ------------------------- // Start a new node with the snapshot - let core = FuelCoreDriver::spawn(&[ + let core = FuelCoreDriver::spawn_feeless(&[ "--debug", "--poa-instant", "true", @@ -345,7 +346,8 @@ async fn test_regenesis_processed_transactions_are_preserved() -> anyhow::Result #[tokio::test(flavor = "multi_thread")] async fn test_regenesis_message_proofs_are_preserved() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); - let core = FuelCoreDriver::spawn(&["--debug", "--poa-instant", "true"]).await?; + let core = + FuelCoreDriver::spawn_feeless(&["--debug", "--poa-instant", "true"]).await?; let base_asset_id = *core .node .shared @@ -386,7 +388,6 @@ async fn test_regenesis_message_proofs_are_preserved() -> anyhow::Result<()> { Default::default(), base_asset_id, )) - .max_fee_limit(1_000_000) .script_gas_limit(1_000_000) .finalize_as_transaction(); diff --git a/tests/tests/state_rewind.rs b/tests/tests/state_rewind.rs index a3a513734c4..c65e298439a 100644 --- a/tests/tests/state_rewind.rs +++ b/tests/tests/state_rewind.rs @@ -63,7 +63,7 @@ fn transfer_transaction(min_amount: u64, rng: &mut StdRng) -> Transaction { #[tokio::test(flavor = "multi_thread")] async fn validate_block_at_any_height__only_transfers() -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); - let driver = FuelCoreDriver::spawn(&[ + let driver = FuelCoreDriver::spawn_feeless(&[ "--debug", "--poa-instant", "true", @@ -139,7 +139,7 @@ async fn rollback_existing_chain_to_target_height_and_verify( blocks_in_the_chain: u32, ) -> anyhow::Result<()> { let mut rng = StdRng::seed_from_u64(1234); - let driver = FuelCoreDriver::spawn(&[ + let driver = FuelCoreDriver::spawn_feeless(&[ "--debug", "--poa-instant", "true", @@ -177,7 +177,7 @@ async fn rollback_existing_chain_to_target_height_and_verify( fuel_core_bin::cli::rollback::exec(command).await?; // Then - let driver = FuelCoreDriver::spawn_with_directory( + let driver = FuelCoreDriver::spawn_feeless_with_directory( temp_dir, &[ "--debug", diff --git a/tests/tests/tx/utxo_validation.rs b/tests/tests/tx/utxo_validation.rs index a032a340dd8..286f5c096a4 100644 --- a/tests/tests/tx/utxo_validation.rs +++ b/tests/tests/tx/utxo_validation.rs @@ -116,7 +116,7 @@ async fn submit_utxo_verified_tx_below_min_gas_price_fails() { // initialize node with higher minimum gas price let mut test_builder = TestSetupBuilder::new(2322u64); - test_builder.min_gas_price = 10; + test_builder.starting_gas_price = 10; let TestContext { client, srv: _dont_drop,