diff --git a/.gitignore b/.gitignore index ab4230b0a1..4185e7af9a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,7 @@ benches/benches-outputs/Cargo.lock benches/benches-outputs/src/test_gas_costs_output.rs .idea .env +node_modules/ +package-lock.json +package.json + diff --git a/CHANGELOG.md b/CHANGELOG.md index fa5a491336..5dfce795b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- [#1713](https://github.com/FuelLabs/fuel-core/pull/1713): Added automatic `impl` of traits `StorageWrite` and `StorageRead` for `StructuredStorage`. Tables that use a `Blueprint` can be read and written using these interfaces provided by structured storage types. - [#1671](https://github.com/FuelLabs/fuel-core/pull/1671): Added a new `Merklized` blueprint that maintains the binary Merkle tree over the storage data. It supports only the insertion of the objects without removing them. - [#1657](https://github.com/FuelLabs/fuel-core/pull/1657): Moved `ContractsInfo` table from `fuel-vm` to on-chain tables, and created version-able `ContractsInfoType` to act as the table's data type. @@ -39,6 +40,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). #### Breaking +- [#1714](https://github.com/FuelLabs/fuel-core/pull/1714): The change bumps the `fuel-vm` to `0.47.1`. It breaks several breaking changes into the protocol: + - All malleable fields are zero during the execution and unavailable through the GTF getters. Accessing them via the memory directly is still possible, but they are zero. + - The `Transaction` doesn't define the gas price anymore. The gas price is defined by the block producer and recorded in the `Mint` transaction at the end of the block. A price of future blocks can be fetched through a [new API nedopoint](https://github.com/FuelLabs/fuel-core/issues/1641) and the price of the last block can be fetch or via the block or another [API endpoint](https://github.com/FuelLabs/fuel-core/issues/1647). + - The `GasPrice` policy is replaced with the `Tip` policy. The user may specify in the native tokens how much he wants to pay the block producer to include his transaction in the block. It is the prioritization mechanism to incentivize the block producer to include users transactions earlier. + - The `MaxFee` policy is mandatory to set. Without it, the transaction pool will reject the transaction. Since the block producer defines the gas price, the only way to control how much user agreed to pay can be done only through this policy. + - The `maturity` field is removed from the `Input::Coin`. The same affect can be achieve with the `Maturity` policy on the transaction and predicate. This changes breaks how input coin is created and removes the passing of this argument. + - The metadata of the `Checked` doesn't contain `max_fee` and `min_fee` anymore. Only `max_gas` and `min_gas`. The `max_fee` is controlled by the user via the `MaxFee` policy. + - Added automatic `impl` of traits `StorageWrite` and `StorageRead` for `StructuredStorage`. Tables that use a `Blueprint` can be read and written using these interfaces provided by structured storage types. + - [#1712](https://github.com/FuelLabs/fuel-core/pull/1712): Make `ContractUtxoInfo` type a version-able enum for use in the `ContractsLatestUtxo`table. - [#1657](https://github.com/FuelLabs/fuel-core/pull/1657): Changed `CROO` gas price type from `Word` to `DependentGasPrice`. The dependent gas price values are dummy values while awaiting updated benchmarks. - [#1671](https://github.com/FuelLabs/fuel-core/pull/1671): The GraphQL API uses block height instead of the block id where it is possible. The transaction status contains `block_height` instead of the `block_id`. diff --git a/Cargo.lock b/Cargo.lock index 9ad9077d30..b282fd4769 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2604,9 +2604,9 @@ dependencies = [ [[package]] name = "fuel-asm" -version = "0.46.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "860332346c3b1c4a893627b591e8db907b54a629d593981e55d833224cdaccc0" +checksum = "d1b088ac4762d59736b90803db239f96c66f2a1a2c616715ef0a9c196b58edc9" dependencies = [ "bitflags 2.4.2", "fuel-types", @@ -3144,9 +3144,9 @@ dependencies = [ [[package]] name = "fuel-crypto" -version = "0.46.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c57b1f04f678e1a2396d1348903631da4b855b9594b3c0c7c589f18caa74658" +checksum = "f6f00ba92f0e13a0dd06a7ad4e9a61221dc43d665519b50bfdb18755aebfdcf0" dependencies = [ "coins-bip32", "coins-bip39", @@ -3165,9 +3165,9 @@ dependencies = [ [[package]] name = "fuel-derive" -version = "0.46.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8cc3c60e13b80177816f53f33d77f917f7cf9a480cb10ff9a64f78672359b1" +checksum = "0b9b265467fe9d3d613a5bae9b8d3005d558d3e830dc416c9836bc16609a000f" dependencies = [ "proc-macro2", "quote", @@ -3177,9 +3177,9 @@ dependencies = [ [[package]] name = "fuel-merkle" -version = "0.46.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7aeaf25539537561819c48c6f1dd79e6d77a238c45f45269a1452b92543fe64" +checksum = "7b21ea035ff06a28791c862496c3b03cfb2d87778476c419724796e945e282ad" dependencies = [ "derive_more", "digest 0.10.7", @@ -3192,15 +3192,15 @@ dependencies = [ [[package]] name = "fuel-storage" -version = "0.46.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f374251c753bb601ad7dbe1efb45b14c170bcfc15f449b6029cc2e3c02dffee2" +checksum = "1048957b744e448840eb9a09cb58c4647dec32e1cf49c0029e5445cbdc86f6d2" [[package]] name = "fuel-tx" -version = "0.46.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac64fab32d5058828ead93c74a41724d1082dc1cdc039415dc4566f5c2305e1" +checksum = "8dc917bb2d1892ef6792624782bed7cee5670f3f836b344f06c4233bb1124aff" dependencies = [ "bitflags 2.4.2", "derivative", @@ -3220,9 +3220,9 @@ dependencies = [ [[package]] name = "fuel-types" -version = "0.46.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9829c1dbdeb29c203f3bd431a69d25bd557fa06ad5bb4dab7773d05e6ffd2824" +checksum = "2444c92791b02016aa1cbab6cfa38e34920e61ec61d564f7bdb3ad8ddfae35de" dependencies = [ "fuel-derive", "hex", @@ -3232,9 +3232,9 @@ dependencies = [ [[package]] name = "fuel-vm" -version = "0.46.0" +version = "0.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8b451aa96729e97704d23238c7c28f6daec2d1761bc406a49856f189ee8b909" +checksum = "5e29c2333edbf3c55906b92a5f3dfb9639e2b0eabc7fd754e7a8e18d67ded3c2" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index f5a0377663..5bbb223aeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ fuel-core-tests = { version = "0.0.0", path = "./tests" } fuel-core-xtask = { version = "0.0.0", path = "./xtask" } # Fuel dependencies -fuel-vm-private = { version = "0.46.0", package = "fuel-vm", default-features = false } +fuel-vm-private = { version = "0.47.1", package = "fuel-vm", default-features = false } # Common dependencies anyhow = "1.0" diff --git a/benches/benches/block_target_gas.rs b/benches/benches/block_target_gas.rs index 455855a108..c678541950 100644 --- a/benches/benches/block_target_gas.rs +++ b/benches/benches/block_target_gas.rs @@ -301,7 +301,7 @@ fn service_with_many_contracts( (0..state_size).map(|_| { storage_key.to_big_endian(key_bytes.as_mut()); storage_key.increase().unwrap(); - (key_bytes, key_bytes) + (key_bytes, key_bytes.to_vec()) }), ) .unwrap(); @@ -362,14 +362,12 @@ fn run_with_service_with_extra_inputs( ); tx_builder .script_gas_limit(TARGET_BLOCK_GAS_LIMIT - BASE) - .gas_price(1) .add_unsigned_coin_input( SecretKey::random(rng), rng.gen(), u32::MAX as u64, AssetId::BASE, Default::default(), - Default::default(), ); for contract_id in &contract_ids { let input_count = tx_builder.inputs().len(); diff --git a/benches/benches/block_target_gas_set/contract.rs b/benches/benches/block_target_gas_set/contract.rs index f4b1b8363b..de32d773d1 100644 --- a/benches/benches/block_target_gas_set/contract.rs +++ b/benches/benches/block_target_gas_set/contract.rs @@ -332,7 +332,6 @@ pub fn run_contract(group: &mut BenchmarkGroup) { AssetId::zeroed(), Default::default(), Default::default(), - Default::default(), predicate, vec![], ); diff --git a/benches/benches/state.rs b/benches/benches/state.rs index d30c01b5e0..fb3027c654 100644 --- a/benches/benches/state.rs +++ b/benches/benches/state.rs @@ -35,7 +35,7 @@ fn setup(db: &mut Database, contract: &ContractId, n: usize) { let mut rng_values = thread_rng(); let gen_values = || -> Bytes32 { rng_values.gen() }; - let state_values = iter::repeat_with(gen_values).take(n); + let state_values = iter::repeat_with(gen_values).map(|b| b.to_vec()).take(n); // State key-values let state_key_values = state_keys.zip(state_values); @@ -66,7 +66,7 @@ fn insert_state_single_contract_database(c: &mut Criterion) { ); let start = std::time::Instant::now(); inner_db - .merkle_contract_state_insert(&contract, &state, &value) + .contract_state_insert(&contract, &state, value.as_slice()) .expect("failed to insert state into transaction"); elapsed_time += start.elapsed(); } @@ -127,7 +127,7 @@ fn insert_state_single_contract_transaction(c: &mut Criterion) { ); let start = std::time::Instant::now(); inner_db - .merkle_contract_state_insert(&contract, &state, &value) + .contract_state_insert(&contract, &state, value.as_slice()) .expect("failed to insert state into transaction"); elapsed_time += start.elapsed(); } @@ -191,7 +191,7 @@ fn insert_state_multiple_contracts_database(c: &mut Criterion) { ); let start = std::time::Instant::now(); inner_db - .merkle_contract_state_insert(&contract, &state, &value) + .contract_state_insert(&contract, &state, value.as_slice()) .expect("failed to insert state into transaction"); elapsed_time += start.elapsed(); } @@ -255,7 +255,7 @@ fn insert_state_multiple_contracts_transaction(c: &mut Criterion) { ); let start = std::time::Instant::now(); inner_db - .merkle_contract_state_insert(&contract, &state, &value) + .contract_state_insert(&contract, &state, value.as_slice()) .expect("failed to insert state into transaction"); elapsed_time += start.elapsed(); } diff --git a/benches/benches/transaction_throughput.rs b/benches/benches/transaction_throughput.rs index 3e983f3d5f..c495f1b134 100644 --- a/benches/benches/transaction_throughput.rs +++ b/benches/benches/transaction_throughput.rs @@ -158,14 +158,12 @@ fn signed_transfers(c: &mut Criterion) { let generator = |rng: &mut StdRng| { TransactionBuilder::script(vec![], vec![]) .script_gas_limit(10000) - .gas_price(1) .add_unsigned_coin_input( SecretKey::random(rng), rng.gen(), 1000, Default::default(), Default::default(), - Default::default(), ) .add_unsigned_coin_input( SecretKey::random(rng), @@ -173,7 +171,6 @@ fn signed_transfers(c: &mut Criterion) { 1000, Default::default(), Default::default(), - Default::default(), ) .add_output(Output::coin(rng.gen(), 50, AssetId::default())) .add_output(Output::change(rng.gen(), 0, AssetId::default())) @@ -189,7 +186,6 @@ fn predicate_transfers(c: &mut Criterion) { let mut tx = TransactionBuilder::script(vec![], vec![]) .script_gas_limit(10000) - .gas_price(1) .add_input(Input::coin_predicate( rng.gen(), owner, @@ -197,7 +193,6 @@ fn predicate_transfers(c: &mut Criterion) { Default::default(), Default::default(), Default::default(), - Default::default(), predicate.clone(), vec![], )) @@ -208,7 +203,6 @@ fn predicate_transfers(c: &mut Criterion) { Default::default(), Default::default(), Default::default(), - Default::default(), predicate, vec![], )) @@ -258,7 +252,6 @@ fn predicate_transfers_eck1(c: &mut Criterion) { let mut tx = TransactionBuilder::script(vec![], vec![]) .script_gas_limit(10000) - .gas_price(1) .add_input(Input::coin_predicate( rng.gen(), owner, @@ -266,7 +259,6 @@ fn predicate_transfers_eck1(c: &mut Criterion) { Default::default(), Default::default(), Default::default(), - Default::default(), predicate.clone(), predicate_data.clone(), )) @@ -277,7 +269,6 @@ fn predicate_transfers_eck1(c: &mut Criterion) { Default::default(), Default::default(), Default::default(), - Default::default(), predicate, predicate_data, )) diff --git a/benches/benches/vm_initialization.rs b/benches/benches/vm_initialization.rs index 518e360ec0..a6698820d8 100644 --- a/benches/benches/vm_initialization.rs +++ b/benches/benches/vm_initialization.rs @@ -48,7 +48,6 @@ fn transaction( rng.gen(), AssetId::BASE, rng.gen(), - rng.gen(), 0, vec![255; 1], vec![255; 1], @@ -67,7 +66,7 @@ fn transaction( script, script_data, Policies::new() - .with_gas_price(0) + .with_max_fee(0) .with_maturity(0.into()) .with_max_fee(Word::MAX), inputs, @@ -103,7 +102,8 @@ pub fn vm_initialization(c: &mut Criterion) { let mut vm = black_box( Interpreter::<_, Script, NotSupportedEcal>::with_memory_storage(), ); - black_box(vm.init_script(tx.clone())) + let ready_tx = tx.clone().test_into_ready(); + black_box(vm.init_script(ready_tx)) .expect("Should be able to execute transaction"); }) }); diff --git a/benches/benches/vm_set/blockchain.rs b/benches/benches/vm_set/blockchain.rs index 7d01503807..7673f20c28 100644 --- a/benches/benches/vm_set/blockchain.rs +++ b/benches/benches/vm_set/blockchain.rs @@ -72,7 +72,7 @@ impl BenchDb { (0..state_size).map(|_| { storage_key.to_big_endian(key_bytes.as_mut()); storage_key.increase().unwrap(); - (key_bytes, key_bytes) + (key_bytes, key_bytes.to_vec()) }), )?; @@ -504,7 +504,6 @@ pub fn run(c: &mut Criterion) { AssetId::zeroed(), Default::default(), Default::default(), - Default::default(), predicate, vec![], ); @@ -590,7 +589,6 @@ pub fn run(c: &mut Criterion) { AssetId::zeroed(), Default::default(), Default::default(), - Default::default(), predicate, vec![], ); diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 98f4e59b3e..4ccf413c1a 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -342,7 +342,7 @@ impl TryFrom for VmBenchPrepared { { return Err(anyhow::anyhow!( "a prepare script should not call/return into different contexts.", - )) + )); } let prepare_script = prepare_script @@ -437,7 +437,6 @@ impl TryFrom for VmBenchPrepared { params.fee_params.gas_per_byte = 0; params.gas_costs = GasCosts::free(); let mut tx = tx - .gas_price(gas_price) .script_gas_limit(gas_limit) .maturity(maturity) .with_params(params.clone()) @@ -445,8 +444,9 @@ impl TryFrom for VmBenchPrepared { tx.estimate_predicates(&CheckPredicateParams::from(¶ms)) .unwrap(); let tx = tx.into_checked(height, ¶ms).unwrap(); + let interpreter_params = InterpreterParams::new(gas_price, ¶ms); - let mut txtor = Transactor::new(db, InterpreterParams::from(¶ms)); + let mut txtor = Transactor::new(db, interpreter_params); txtor.transact(tx); diff --git a/bin/e2e-test-client/src/test_context.rs b/bin/e2e-test-client/src/test_context.rs index c383c70264..007cefcf01 100644 --- a/bin/e2e-test-client/src/test_context.rs +++ b/bin/e2e-test-client/src/test_context.rs @@ -41,6 +41,7 @@ use fuel_core_types::{ canonical::Serialize, Address, AssetId, + Bytes32, }, fuel_vm::SecretKey, }; @@ -51,7 +52,7 @@ use crate::config::{ }; // The base amount needed to cover the cost of a simple transaction -pub const BASE_AMOUNT: u64 = 1_000_000; +pub const BASE_AMOUNT: u64 = 100_000_000; pub struct TestContext { pub alice: Wallet, @@ -132,13 +133,13 @@ impl Wallet { results = response.results; // check if page has the utxos we're looking for if results.iter().any(|coin| coin.utxo_id == utxo_id) { - return Ok(true) + return Ok(true); } // otherwise update the cursor to check the next page if response.has_next_page { cursor = response.cursor; } else { - break + break; } } @@ -162,7 +163,7 @@ impl Wallet { // build transaction let mut tx = TransactionBuilder::script(Default::default(), Default::default()); - tx.gas_price(1); + tx.max_fee_limit(BASE_AMOUNT); tx.script_gas_limit(0); for coin in coins { @@ -173,7 +174,6 @@ impl Wallet { coin.amount, coin.asset_id, Default::default(), - coin.maturity.into(), ); } } @@ -235,7 +235,7 @@ impl Wallet { .chain(0u64.to_bytes().into_iter()) .collect(), ); - tx.gas_price(1); + tx.max_fee_limit(BASE_AMOUNT); tx.script_gas_limit(BASE_AMOUNT); tx.add_input(Input::contract( @@ -253,7 +253,6 @@ impl Wallet { coin.amount, coin.asset_id, Default::default(), - coin.maturity.into(), ); } } @@ -300,7 +299,7 @@ impl Wallet { } pub async fn deploy_contract(&self, config: ContractConfig) -> anyhow::Result<()> { - let asset_id = AssetId::zeroed(); + let asset_id = AssetId::BASE; let total_amount = BASE_AMOUNT; // select coins let coins = &self @@ -318,11 +317,10 @@ impl Wallet { let slots = state .unwrap_or_default() .into_iter() - .map(|(key, value)| StorageSlot::new(key, value)) + .map(|(key, value)| StorageSlot::new(key, vec_to_bytes_32(value))) .collect::>(); let state_root = Contract::initial_state_root(slots.iter()); let mut tx = TransactionBuilder::create(bytes.into(), salt, slots); - tx.gas_price(1); for coin in coins { if let CoinType::Coin(coin) = coin { @@ -332,7 +330,6 @@ impl Wallet { coin.amount, coin.asset_id, Default::default(), - coin.maturity.into(), ); } } @@ -345,6 +342,7 @@ impl Wallet { amount: 0, asset_id, }); + tx.max_fee_limit(BASE_AMOUNT); let tx = tx.finalize(); println!("The size of the transaction is {}", tx.size()); @@ -358,13 +356,20 @@ impl Wallet { if let TransactionStatus::Failure { .. } | TransactionStatus::SqueezedOut { .. } = &status { - return Err(anyhow!(format!("unexpected transaction status {status:?}"))) + return Err(anyhow!(format!("unexpected transaction status {status:?}"))); } Ok(()) } } +// TODO: Remove when dynamic storage slots are supported. +fn vec_to_bytes_32(vec: Vec) -> Bytes32 { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(&vec); + bytes.into() +} + pub struct TransferResult { pub tx_id: TxId, pub transferred_utxo: UtxoId, diff --git a/bin/e2e-test-client/src/tests/script.rs b/bin/e2e-test-client/src/tests/script.rs index 0d620acaa8..1cf8e20c60 100644 --- a/bin/e2e-test-client/src/tests/script.rs +++ b/bin/e2e-test-client/src/tests/script.rs @@ -5,10 +5,7 @@ use crate::test_context::{ use fuel_core_chain_config::ContractConfig; use fuel_core_types::{ fuel_tx::{ - field::{ - GasPrice, - ScriptGasLimit, - }, + field::ScriptGasLimit, Receipt, ScriptExecutionResult, Transaction, @@ -31,7 +28,7 @@ pub async fn receipts(ctx: &TestContext) -> Result<(), Failed> { .await??; let status = result.status; if !result.success { - return Err(format!("transfer failed with status {status:?}").into()) + return Err(format!("transfer failed with status {status:?}").into()); } println!("The tx id of the script: {}", result.tx_id); @@ -48,7 +45,7 @@ pub async fn receipts(ctx: &TestContext) -> Result<(), Failed> { if receipts.is_none() { return Err( format!("Receipts are empty for query_number {query_number}").into(), - ) + ); } } @@ -135,7 +132,6 @@ pub async fn non_specific_transaction(ctx: &TestContext) -> Result<(), Failed> { if let Some(script) = dry_run.as_script_mut() { *script.script_gas_limit_mut() = 100000; - script.set_gas_price(0); } _dry_runs(ctx, &[dry_run], 1000, DryRunResult::MayFail).await @@ -182,7 +178,7 @@ async fn _dry_runs( if tx_status.result.receipts().is_empty() { return Err( format!("Receipts are empty for query_number {query_number}").into(), - ) + ); } assert!(tx.id(&chain_info.consensus_parameters.chain_id) == tx_status.id); 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 5facfcc8cf..c87b440002 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 @@ -4099,8 +4099,8 @@ 162 ], "policies": { - "bits": "GasPrice", - "values": [0, 0, 0, 0] + "bits": "MaxFee", + "values": [0, 0, 0, 100000] }, "inputs": [ { @@ -4137,6 +4137,26 @@ "predicate": null, "predicate_data": null } + }, + { + "CoinSigned": { + "utxo_id": { + "tx_id": "c49d65de61cf04588a764b557d25cc6c6b4bc0d7429227e2a21e61c213b3a3e2", + "output_index": 1 + }, + "owner": "f1e92c42b90934aa6372e30bc568a326f6e66a1a0288595e6e3fbd392a4f3e6e", + "amount": 10599410012256088338, + "asset_id": "0000000000000000000000000000000000000000000000000000000000000000", + "tx_pointer": { + "block_height": 0, + "tx_index": 0 + }, + "witness_index": 0, + "maturity": 0, + "predicate_gas_used": null, + "predicate": null, + "predicate_data": null + } } ], "outputs": [ diff --git a/bin/e2e-test-client/src/tests/test_data/non_specific_tx.raw b/bin/e2e-test-client/src/tests/test_data/non_specific_tx.raw index 08002701b0..80c23be64e 100644 --- a/bin/e2e-test-client/src/tests/test_data/non_specific_tx.raw +++ b/bin/e2e-test-client/src/tests/test_data/non_specific_tx.raw @@ -1 +1 @@ -0x00000000000000000000000005f5e1000000000000000004000000000000000000000000000000000000000000000001000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000008b5a47933cbb7ddbc0392a45a124a2a01f17c657d34d4951324003a2f9aff24a000000000000000157cd0f26d30e6e0361742800f0cb39b87d2bd58e052c4389d7e507e39e504a01000000001d34a76e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000000000000000000000000000000000000000000000eeb709945b9058c3d50f3922bd1b49f92ced2950a9ecaf810aa7829295550cd200000000000027100000000000000000000000000000000000000000000000000000000000000000000000000000000357cd0f26d30e6e0361742800f0cb39b87d2bd58e052c4389d7e507e39e504a01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file +0x00000000000000000000000005f5e1000000000000000004000000000000000000000000000000080000000000000001000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000186a000000000000000008b5a47933cbb7ddbc0392a45a124a2a01f17c657d34d4951324003a2f9aff24a000000000000000157cd0f26d30e6e0361742800f0cb39b87d2bd58e052c4389d7e507e39e504a01000000001d34a76e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeb709945b9058c3d50f3922bd1b49f92ced2950a9ecaf810aa7829295550cd200000000000027100000000000000000000000000000000000000000000000000000000000000000000000000000000357cd0f26d30e6e0361742800f0cb39b87d2bd58e052c4389d7e507e39e504a01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/bin/e2e-test-client/tests/integration_tests.rs b/bin/e2e-test-client/tests/integration_tests.rs index 54840381a7..6b32340434 100644 --- a/bin/e2e-test-client/tests/integration_tests.rs +++ b/bin/e2e-test-client/tests/integration_tests.rs @@ -108,6 +108,7 @@ fn dev_config() -> Config { ); config.chain_conf = chain_config; + config.block_producer.gas_price = 1; config.block_producer.coinbase_recipient = Some( ContractId::from_str( "0x7777777777777777777777777777777777777777777777777777777777777777", diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index cc0ff129b8..0573e42917 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -323,6 +323,7 @@ impl Command { block_producer: ProducerConfig { utxo_validation, coinbase_recipient, + gas_price: min_gas_price, metrics, }, block_importer, diff --git a/crates/chain-config/src/config.rs b/crates/chain-config/src/config.rs index 2ec36889f4..5b32fc413d 100644 --- a/crates/chain-config/src/config.rs +++ b/crates/chain-config/src/config.rs @@ -230,7 +230,8 @@ mod tests { let mut rng = StdRng::seed_from_u64(1); let state = if state { let test_key: Bytes32 = rng.gen(); - let test_value: Bytes32 = rng.gen(); + let bytes: Bytes32 = rng.gen(); + let test_value: Vec = bytes.to_vec(); Some(vec![(test_key, test_value)]) } else { None @@ -280,7 +281,6 @@ mod tests { let output_index: Option = Some(rng.gen()); let block_created = Some(rng.next_u32().into()); let block_created_tx_idx = Some(rng.gen()); - let maturity = Some(rng.next_u32().into()); let owner = rng.gen(); let amount = rng.gen(); let asset_id = rng.gen(); @@ -292,7 +292,6 @@ mod tests { output_index, tx_pointer_block_height: block_created, tx_pointer_tx_idx: block_created_tx_idx, - maturity, owner, amount, asset_id, diff --git a/crates/chain-config/src/config/chain.rs b/crates/chain-config/src/config/chain.rs index 20c7c7d941..a47dea3bc4 100644 --- a/crates/chain-config/src/config/chain.rs +++ b/crates/chain-config/src/config/chain.rs @@ -160,7 +160,6 @@ impl ChainConfig { output_index: utxo_id.as_ref().map(|u| u.output_index()), tx_pointer_block_height: None, tx_pointer_tx_idx: None, - maturity: None, owner: address, amount, asset_id: Default::default(), diff --git a/crates/chain-config/src/config/coin.rs b/crates/chain-config/src/config/coin.rs index 01bba78be0..440f620a4e 100644 --- a/crates/chain-config/src/config/coin.rs +++ b/crates/chain-config/src/config/coin.rs @@ -45,9 +45,6 @@ pub struct CoinConfig { #[serde_as(as = "Option")] #[serde(default)] pub tx_pointer_tx_idx: Option, - #[serde_as(as = "Option")] - #[serde(default)] - pub maturity: Option, #[serde_as(as = "HexType")] pub owner: Address, #[serde_as(as = "HexNumber")] @@ -61,14 +58,12 @@ impl GenesisCommitment for CompressedCoin { let owner = self.owner(); let amount = self.amount(); let asset_id = self.asset_id(); - let maturity = self.maturity(); let tx_pointer = self.tx_pointer(); let coin_hash = *Hasher::default() .chain(owner) .chain(amount.to_be_bytes()) .chain(asset_id) - .chain((*maturity).to_be_bytes()) .chain(tx_pointer.block_height().to_be_bytes()) .chain(tx_pointer.tx_index().to_be_bytes()) .finalize(); diff --git a/crates/chain-config/src/config/contract.rs b/crates/chain-config/src/config/contract.rs index 0429d92d0d..a64e685cf9 100644 --- a/crates/chain-config/src/config/contract.rs +++ b/crates/chain-config/src/config/contract.rs @@ -26,7 +26,7 @@ use serde_with::{ #[skip_serializing_none] #[serde_as] -#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[derive(Default, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct ContractConfig { #[serde_as(as = "HexType")] pub contract_id: ContractId, @@ -36,7 +36,7 @@ pub struct ContractConfig { pub salt: Salt, #[serde_as(as = "Option>")] #[serde(default)] - pub state: Option>, + pub state: Option)>>, #[serde_as(as = "Option>")] #[serde(default)] pub balances: Option>, @@ -67,8 +67,16 @@ impl ContractConfig { let bytes = &self.code; let salt = self.salt; let slots = self.state.clone().map(|slots| { + // TODO: When supporting dynamic storage, use byte vector directly instead of mapping it into a Bytes32. slots .into_iter() + .map(|(key, value)| { + ( + key, + TryFrom::try_from(value.as_slice()) + .expect("Expected 32 byte storage slot"), + ) + }) .map(|(key, value)| StorageSlot::new(key, value)) .collect::>() }); diff --git a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_coin_state.snap b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_coin_state.snap index 86468fcca9..40950834e5 100644 --- a/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_coin_state.snap +++ b/crates/chain-config/src/snapshots/fuel_core_chain_config__config__tests__snapshot_simple_coin_state.snap @@ -12,10 +12,9 @@ expression: json "output_index": "0xc9", "tx_pointer_block_height": "0xe46d13fb", "tx_pointer_tx_idx": "0xf4a4", - "maturity": "0x63f8fe17", - "owner": "0xa2d8d9ed086c0f3f03f94ec89a67bfdc6a1e22b69d667e5ac8055d00a3f57027", - "amount": "0x0d1865b14bc80dbc", - "asset_id": "0x0d053242a9719dca480b4f4910fa60671f2068086498c72e30fc8b969e083932" + "owner": "0x17a2d8d9ed086c0f3f03f94ec89a67bfdc6a1e22b69d667e5ac8055d00a3f570", + "amount": "0x4bc80dbc43bd0a27", + "asset_id": "0xb10d053242a9719dca480b4f4910fa60671f2068086498c72e30fc8b969e0839" } ] }, diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index 0726ef81df..a5b2f4bc9c 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -113,7 +113,6 @@ type Coin { owner: Address! amount: U64! assetId: AssetId! - maturity: U32! """ TxPointer - the height of the block this coin was created in """ @@ -498,7 +497,6 @@ type InputCoin { assetId: AssetId! txPointer: TxPointer! witnessIndex: Int! - maturity: U32! predicateGasUsed: U64! predicate: HexString! predicateData: HexString! @@ -746,7 +744,7 @@ type PoAConsensus { } type Policies { - gasPrice: U64 + tip: U64 witnessLimit: U64 maturity: U32 maxFee: U64 @@ -964,7 +962,6 @@ type Transaction { inputContracts: [ContractId!] inputContract: InputContract policies: Policies - gasPrice: U64 scriptGasLimit: U64 maturity: U32 mintAmount: U64 diff --git a/crates/client/src/client/schema/coins.rs b/crates/client/src/client/schema/coins.rs index 4aa265a853..dbe7999d98 100644 --- a/crates/client/src/client/schema/coins.rs +++ b/crates/client/src/client/schema/coins.rs @@ -25,7 +25,7 @@ pub struct CoinByIdArgs { variables = "CoinByIdArgs" )] pub struct CoinByIdQuery { - #[arguments(utxoId: $utxo_id)] + #[arguments(utxoId: $ utxo_id)] pub coin: Option, } @@ -87,7 +87,7 @@ impl From<(Address, AssetId, PaginationRequest)> for CoinsConnectionArgs variables = "CoinsConnectionArgs" )] pub struct CoinsQuery { - #[arguments(filter: $filter, after: $after, before: $before, first: $first, last: $last)] + #[arguments(filter: $ filter, after: $ after, before: $ before, first: $ first, last: $ last)] pub coins: CoinConnection, } @@ -112,7 +112,6 @@ pub struct Coin { pub block_created: U32, pub asset_id: AssetId, pub utxo_id: UtxoId, - pub maturity: U32, pub owner: Address, } @@ -208,7 +207,7 @@ impl From for CoinsToSpendArgs { variables = "CoinsToSpendArgs" )] pub struct CoinsToSpendQuery { - #[arguments(owner: $owner, queryPerAsset: $query_per_asset, excludedIds: $excluded_ids)] + #[arguments(owner: $ owner, queryPerAsset: $ query_per_asset, excludedIds: $ excluded_ids)] pub coins_to_spend: Vec>, } diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__coins__tests__coin_by_id_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__coins__tests__coin_by_id_query_gql_output.snap index 37bb88a5ba..2aeac0a537 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__coins__tests__coin_by_id_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__coins__tests__coin_by_id_query_gql_output.snap @@ -8,9 +8,6 @@ query($utxoId: UtxoId!) { blockCreated assetId utxoId - maturity owner } } - - diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__coins__tests__coins_connection_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__coins__tests__coins_connection_query_gql_output.snap index d8adea12c1..1c4baf4c3f 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__coins__tests__coins_connection_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__coins__tests__coins_connection_query_gql_output.snap @@ -11,7 +11,6 @@ query($filter: CoinFilterInput!, $after: String, $before: String, $first: Int, $ blockCreated assetId utxoId - maturity owner } } @@ -23,5 +22,3 @@ query($filter: CoinFilterInput!, $after: String, $before: String, $first: Int, $ } } } - - diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transparent_transaction_by_id_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transparent_transaction_by_id_query_gql_output.snap index 7b9508e7fd..74a9933284 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transparent_transaction_by_id_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transparent_transaction_by_id_query_gql_output.snap @@ -25,7 +25,6 @@ query($id: TransactionId!) { assetId txPointer witnessIndex - maturity predicateGasUsed predicate predicateData @@ -184,7 +183,7 @@ query($id: TransactionId!) { script scriptData policies { - gasPrice + tip maturity witnessLimit maxFee diff --git a/crates/client/src/client/schema/tx/transparent_tx.rs b/crates/client/src/client/schema/tx/transparent_tx.rs index 2d357ccfa8..94998bec75 100644 --- a/crates/client/src/client/schema/tx/transparent_tx.rs +++ b/crates/client/src/client/schema/tx/transparent_tx.rs @@ -45,7 +45,7 @@ use itertools::Itertools; variables = "TxIdArgs" )] pub struct TransactionQuery { - #[arguments(id: $id)] + #[arguments(id: $ id)] pub transaction: Option, } @@ -56,7 +56,7 @@ pub struct TransactionQuery { variables = "ConnectionArgs" )] pub struct TransactionsQuery { - #[arguments(after: $after, before: $before, first: $first, last: $last)] + #[arguments(after: $ after, before: $ before, first: $ first, last: $ last)] pub transactions: TransactionConnection, } @@ -218,7 +218,7 @@ impl TryFrom for fuel_tx::Transaction { .into_iter() .map(|slot| { if slot.0 .0.len() != 64 { - return Err(ConversionError::BytesLength) + return Err(ConversionError::BytesLength); } let key = &slot.0 .0[0..32]; let value = &slot.0 .0[32..]; @@ -318,7 +318,6 @@ pub struct InputCoin { pub asset_id: AssetId, pub tx_pointer: TxPointer, pub witness_index: i32, - pub maturity: U32, pub predicate_gas_used: U64, pub predicate: HexString, pub predicate_data: HexString, @@ -362,7 +361,6 @@ impl TryFrom for fuel_tx::Input { coin.asset_id.into(), coin.tx_pointer.into(), coin.witness_index.try_into()?, - coin.maturity.into(), ) } else { fuel_tx::Input::coin_predicate( @@ -371,7 +369,6 @@ impl TryFrom for fuel_tx::Input { coin.amount.into(), coin.asset_id.into(), coin.tx_pointer.into(), - coin.maturity.into(), coin.predicate_gas_used.into(), coin.predicate.into(), coin.predicate_data.into(), @@ -533,7 +530,7 @@ impl TryFrom for output::contract::Contract { #[derive(cynic::QueryFragment, Debug)] #[cynic(schema_path = "./assets/schema.sdl")] pub struct Policies { - pub gas_price: Option, + pub tip: Option, pub maturity: Option, pub witness_limit: Option, pub max_fee: Option, @@ -542,7 +539,7 @@ pub struct Policies { impl From for fuel_tx::policies::Policies { fn from(value: Policies) -> Self { let mut policies = fuel_tx::policies::Policies::new(); - policies.set(PolicyType::GasPrice, value.gas_price.map(Into::into)); + policies.set(PolicyType::Tip, value.tip.map(Into::into)); policies.set( PolicyType::Maturity, value.maturity.map(|maturity| maturity.0 as u64), diff --git a/crates/client/src/client/types/coins.rs b/crates/client/src/client/types/coins.rs index 5b069c07ae..379e75601a 100644 --- a/crates/client/src/client/types/coins.rs +++ b/crates/client/src/client/types/coins.rs @@ -32,7 +32,6 @@ pub struct Coin { pub block_created: u32, pub asset_id: AssetId, pub utxo_id: UtxoId, - pub maturity: u32, pub owner: Address, } @@ -66,7 +65,6 @@ impl From for Coin { block_created: value.block_created.into(), asset_id: value.asset_id.into(), utxo_id: value.utxo_id.into(), - maturity: value.maturity.into(), owner: value.owner.into(), } } diff --git a/crates/fuel-core/src/database/coin.rs b/crates/fuel-core/src/database/coin.rs index bfaaab7c04..d2e9eb6469 100644 --- a/crates/fuel-core/src/database/coin.rs +++ b/crates/fuel-core/src/database/coin.rs @@ -71,7 +71,6 @@ impl Database { output_index: Some(utxo_id.output_index()), tx_pointer_block_height: Some(coin.tx_pointer().block_height()), tx_pointer_tx_idx: Some(coin.tx_pointer().tx_index()), - maturity: Some(*coin.maturity()), owner: *coin.owner(), amount: *coin.amount(), asset_id: *coin.asset_id(), diff --git a/crates/fuel-core/src/database/contracts.rs b/crates/fuel-core/src/database/contracts.rs index b5d0406ccd..375911f90a 100644 --- a/crates/fuel-core/src/database/contracts.rs +++ b/crates/fuel-core/src/database/contracts.rs @@ -15,7 +15,6 @@ use fuel_core_storage::{ }; use fuel_core_types::fuel_types::{ AssetId, - Bytes32, ContractId, Word, }; @@ -50,8 +49,8 @@ impl Database { let state = Some( self.iter_all_by_prefix::(Some(contract_id.as_ref())) - .map(|res| -> StorageResult<(Bytes32, Bytes32)> { - let (key, value) = res?; + .map(|res| { + let (key, value) = res.map(|(key, value)| (key, value.0))?; Ok((*key.state_key(), value)) }) diff --git a/crates/fuel-core/src/database/state.rs b/crates/fuel-core/src/database/state.rs index 01c3971e1d..e95930cd74 100644 --- a/crates/fuel-core/src/database/state.rs +++ b/crates/fuel-core/src/database/state.rs @@ -20,7 +20,7 @@ impl Database { slots: S, ) -> Result<(), StorageError> where - S: Iterator, + S: Iterator)>, { let slots = slots .map(|(key, value)| (ContractsStateKey::new(contract_id, &key), value)) @@ -28,7 +28,7 @@ impl Database { #[allow(clippy::map_identity)] <_ as StorageBatchMutate>::init_storage( &mut self.data, - &mut slots.iter().map(|(key, value)| (key, value)), + &mut slots.iter().map(|(key, value)| (key, value.as_slice())), ) } } @@ -58,7 +58,7 @@ mod tests { }; let rng = &mut StdRng::seed_from_u64(1234); - let gen = || Some((random_bytes32(rng), random_bytes32(rng))); + let gen = || Some((random_bytes32(rng), random_bytes32(rng).to_vec())); let data = core::iter::from_fn(gen).take(5_000).collect::>(); let contract_id = ContractId::from([1u8; 32]); @@ -99,8 +99,8 @@ mod tests { .expect("Should get a state from seq database") .unwrap() .into_owned(); - assert_eq!(init_value, value); - assert_eq!(seq_value, value); + assert_eq!(init_value.0, value); + assert_eq!(seq_value.0, value); } } } diff --git a/crates/fuel-core/src/database/storage.rs b/crates/fuel-core/src/database/storage.rs index 9be0a63893..3574f2247d 100644 --- a/crates/fuel-core/src/database/storage.rs +++ b/crates/fuel-core/src/database/storage.rs @@ -49,6 +49,7 @@ use fuel_core_storage::{ StorageMutate, StorageRead, StorageSize, + StorageWrite, }; use std::borrow::Cow; @@ -183,6 +184,30 @@ where } } +impl StorageWrite for Database +where + Description: DatabaseDescription, + M: Mappable, + StructuredStorage>: + StorageWrite + UseStructuredImplementation, +{ + fn write(&mut self, key: &M::Key, buf: &[u8]) -> Result { + <_ as StorageWrite>::write(&mut self.data, key, buf) + } + + fn replace( + &mut self, + key: &M::Key, + buf: &[u8], + ) -> Result<(usize, Option>), Self::Error> { + <_ as StorageWrite>::replace(&mut self.data, key, buf) + } + + fn take(&mut self, key: &M::Key) -> Result>, Self::Error> { + <_ as StorageWrite>::take(&mut self.data, key) + } +} + impl StorageBatchMutate for Database where M: Mappable, diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 4cc401b514..aed3964081 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -59,6 +59,7 @@ mod tests { MintAssetId, OutputContract, Outputs, + Policies, Script as ScriptField, TxPointer as TxPointerTraitTrait, }, @@ -67,9 +68,9 @@ mod tests { contract, Input, }, + policies::PolicyType, Bytes32, Cacheable, - Chargeable, ConsensusParameters, Create, FeeParameters, @@ -378,13 +379,13 @@ mod tests { // the `Mint` transaction from the first block to validate the contract // state transition between blocks. let price = 1; + let amount = 10000; let limit = 0; let gas_price_factor = 1; let script = TxBuilder::new(1u64) .script_gas_limit(limit) - // Set a price for the test - .gas_price(price) - .coin_input(AssetId::BASE, 10000) + .max_fee_limit(amount) + .coin_input(AssetId::BASE, amount) .change_output(AssetId::BASE) .build() .transaction() @@ -417,26 +418,35 @@ mod tests { producer.config.consensus_parameters.gas_costs(), producer.config.consensus_parameters.fee_params(), &script, + price, ) .unwrap() .max_fee(); let invalid_duplicate_tx = script.clone().into(); - let mut block = Block::default(); - block.header_mut().set_block_height(1.into()); - *block.transactions_mut() = vec![script.into(), invalid_duplicate_tx]; - block.header_mut().recalculate_metadata(); + let mut header = PartialBlockHeader::default(); + header.consensus.height = 1.into(); - let ExecutionResult { - block, - skipped_transactions, - .. - } = producer - .execute_and_commit( - ExecutionBlock::Production(block.into()), - Default::default(), - ) - .unwrap(); + let ( + ExecutionResult { + block, + skipped_transactions, + .. + }, + changes, + ) = producer + .execute_without_commit(ExecutionTypes::Production(Components { + header_to_produce: header, + transactions_source: OnceTransactionsSource::new(vec![ + script.into(), + invalid_duplicate_tx, + ]), + gas_price: price, + gas_limit: u64::MAX, + })) + .unwrap() + .into(); + changes.commit().unwrap(); assert_eq!(skipped_transactions.len(), 1); assert_eq!(block.transactions().len(), 2); @@ -475,9 +485,8 @@ mod tests { let script = TxBuilder::new(2u64) .script_gas_limit(limit) - // Set a price for the test - .gas_price(price) - .coin_input(AssetId::BASE, 10000) + .max_fee_limit(amount) + .coin_input(AssetId::BASE, amount) .change_output(AssetId::BASE) .build() .transaction() @@ -487,25 +496,31 @@ mod tests { producer.config.consensus_parameters.gas_costs(), producer.config.consensus_parameters.fee_params(), &script, + price, ) .unwrap() .max_fee(); - let mut block = Block::default(); - block.header_mut().set_block_height(2.into()); - *block.transactions_mut() = vec![script.into()]; - block.header_mut().recalculate_metadata(); + let mut header = PartialBlockHeader::default(); + header.consensus.height = 2.into(); - let ExecutionResult { - block, - skipped_transactions, - .. - } = producer - .execute_and_commit( - ExecutionBlock::Production(block.into()), - Default::default(), - ) - .unwrap(); + let ( + ExecutionResult { + block, + skipped_transactions, + .. + }, + changes, + ) = producer + .execute_without_commit(ExecutionTypes::Production(Components { + header_to_produce: header, + transactions_source: OnceTransactionsSource::new(vec![script.into()]), + gas_price: price, + gas_limit: u64::MAX, + })) + .unwrap() + .into(); + changes.commit().unwrap(); assert_eq!(skipped_transactions.len(), 0); assert_eq!(block.transactions().len(), 2); @@ -581,6 +596,7 @@ mod tests { .execute_without_commit(ExecutionTypes::DryRun(Components { header_to_produce: Default::default(), transactions_source: OnceTransactionsSource::new(vec![script.into()]), + gas_price: 0, gas_limit: u64::MAX, })) .unwrap(); @@ -592,12 +608,12 @@ mod tests { #[test] fn executor_commits_transactions_with_non_zero_coinbase_validation() { let price = 1; + let amount = 10000; let limit = 0; let gas_price_factor = 1; let script = TxBuilder::new(2322u64) .script_gas_limit(limit) - // Set a price for the test - .gas_price(price) + .max_fee_limit(amount) .coin_input(AssetId::BASE, 10000) .change_output(AssetId::BASE) .build() @@ -626,19 +642,19 @@ mod tests { let producer = create_executor(database.clone(), config); - let mut block = Block::default(); - *block.transactions_mut() = vec![script.into()]; - let ExecutionResult { block: produced_block, skipped_transactions, .. } = producer - .execute_and_commit( - ExecutionBlock::Production(block.into()), - Default::default(), - ) - .unwrap(); + .execute_without_commit(ExecutionTypes::Production(Components { + header_to_produce: PartialBlockHeader::default(), + transactions_source: OnceTransactionsSource::new(vec![script.into()]), + gas_price: price, + gas_limit: u64::MAX, + })) + .unwrap() + .into_result(); assert!(skipped_transactions.is_empty()); let produced_txs = produced_block.transactions().to_vec(); @@ -906,12 +922,10 @@ mod tests { fn executor_invalidates_missing_gas_input() { let mut rng = StdRng::seed_from_u64(2322u64); let producer = create_executor(Default::default(), Default::default()); - let consensus_parameters = &producer.config.consensus_parameters; - let verifier = create_executor(Default::default(), Default::default()); let gas_limit = 100; - let gas_price = 1; + let max_fee = 1; let script = TransactionBuilder::script(vec![], vec![]) .add_unsigned_coin_input( SecretKey::random(&mut rng), @@ -919,18 +933,10 @@ mod tests { rng.gen(), rng.gen(), Default::default(), - Default::default(), ) .script_gas_limit(gas_limit) - .gas_price(gas_price) + .max_fee_limit(max_fee) .finalize(); - let max_fee: u64 = script - .max_fee( - consensus_parameters.gas_costs(), - consensus_parameters.fee_params(), - ) - .try_into() - .unwrap(); let tx: Transaction = script.into(); let mut block = PartialFuelBlock { @@ -1060,7 +1066,6 @@ mod tests { 10, Default::default(), Default::default(), - Default::default(), ) .add_output(Output::Change { to: Default::default(), @@ -1222,7 +1227,8 @@ mod tests { // invalidate a block if a tx is missing at least one coin input #[test] fn executor_invalidates_missing_coin_input() { - let tx: Transaction = Transaction::default(); + let mut tx: Script = Script::default(); + tx.policies_mut().set(PolicyType::MaxFee, Some(0)); let executor = create_executor( Database::default(), @@ -1234,7 +1240,7 @@ mod tests { let block = PartialFuelBlock { header: Default::default(), - transactions: vec![tx], + transactions: vec![tx.into()], }; let ExecutionResult { @@ -1468,7 +1474,7 @@ mod tests { #[test] fn skipped_txs_not_affect_order() { - // `tx1` is invalid because it doesn't have inputs for gas. + // `tx1` is invalid because it doesn't have inputs for max fee. // `tx2` is a `Create` transaction with some code inside. // `tx3` is a `Script` transaction that depends on `tx2`. It will be skipped // if `tx2` is not executed before `tx3`. @@ -1478,7 +1484,7 @@ mod tests { let tx1 = TransactionBuilder::script(vec![], vec![]) .add_random_fee_input() .script_gas_limit(1000000) - .gas_price(1000000) + .tip(1000000) .finalize_as_transaction(); let (tx2, tx3) = setup_executable_script(); @@ -1889,8 +1895,14 @@ mod tests { let ExecutionResult { block, tx_status, .. } = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) - .unwrap(); + .execute_without_commit(ExecutionTypes::Production(Components { + header_to_produce: block.header, + transactions_source: OnceTransactionsSource::new(block.transactions), + gas_price: 0, + gas_limit: u64::MAX, + })) + .unwrap() + .into_result(); assert!(matches!( tx_status[1].result, TransactionExecutionResult::Success { .. } @@ -1898,6 +1910,12 @@ mod tests { let tx = block.transactions()[0].as_script().unwrap(); assert_eq!(tx.inputs()[0].balance_root(), balance_root); assert_eq!(tx.inputs()[0].state_root(), state_root); + + executor + .execute_without_commit::(ExecutionTypes::Validation( + block, + )) + .expect("Validation of block should be successful"); } #[test] @@ -1972,7 +1990,6 @@ mod tests { 100, Default::default(), Default::default(), - Default::default(), ) .add_output(Output::Change { to: Default::default(), @@ -2076,17 +2093,22 @@ mod tests { let setup = create_executor(db.clone(), Default::default()); - setup + let ExecutionResult { + skipped_transactions, + .. + } = setup .execute_and_commit( ExecutionBlock::Production(first_block), Default::default(), ) .unwrap(); + assert!(skipped_transactions.is_empty()); let producer_view = db.transaction().deref_mut().clone(); let producer = create_executor(producer_view, Default::default()); let ExecutionResult { block: second_block, + skipped_transactions, .. } = producer .execute_and_commit( @@ -2094,6 +2116,7 @@ mod tests { Default::default(), ) .unwrap(); + assert!(skipped_transactions.is_empty()); let verifier = create_executor(db, Default::default()); let verify_result = verifier.execute_and_commit( @@ -2686,7 +2709,6 @@ mod tests { 1000, base_asset_id, Default::default(), - Default::default(), ) .finalize(); @@ -2712,7 +2734,6 @@ mod tests { coin.set_owner(*coin_input.input_owner().unwrap()); coin.set_amount(coin_input.amount().unwrap()); coin.set_asset_id(*coin_input.asset_id(&base_asset_id).unwrap()); - coin.set_maturity(coin_input.maturity().unwrap()); coin.set_tx_pointer(TxPointer::new(Default::default(), block_tx_idx)); database .storage::() @@ -2757,7 +2778,6 @@ mod tests { 1000, base_asset_id, Default::default(), - Default::default(), ) .finalize(); @@ -2784,7 +2804,6 @@ mod tests { coin.set_owner(*coin_input.input_owner().unwrap()); coin.set_amount(coin_input.amount().unwrap()); coin.set_asset_id(*coin_input.asset_id(&base_asset_id).unwrap()); - coin.set_maturity(coin_input.maturity().unwrap()); database .storage::() .insert(coin_input.utxo_id().unwrap(), &coin) diff --git a/crates/fuel-core/src/p2p_test_helpers.rs b/crates/fuel-core/src/p2p_test_helpers.rs index a93d79a8d4..06bf35c1b0 100644 --- a/crates/fuel-core/src/p2p_test_helpers.rs +++ b/crates/fuel-core/src/p2p_test_helpers.rs @@ -206,7 +206,6 @@ pub async fn make_nodes( initial_coin.amount, initial_coin.asset_id, Default::default(), - Default::default(), ) .finalize_as_transaction(); diff --git a/crates/fuel-core/src/schema/coins.rs b/crates/fuel-core/src/schema/coins.rs index 476058016b..3e8200b07e 100644 --- a/crates/fuel-core/src/schema/coins.rs +++ b/crates/fuel-core/src/schema/coins.rs @@ -60,10 +60,6 @@ impl Coin { self.0.asset_id.into() } - async fn maturity(&self) -> U32 { - self.0.maturity.into() - } - /// TxPointer - the height of the block this coin was created in async fn block_created(&self) -> U32 { u32::from(self.0.tx_pointer.block_height()).into() diff --git a/crates/fuel-core/src/schema/dap.rs b/crates/fuel-core/src/schema/dap.rs index c54f668060..bb6d3450fa 100644 --- a/crates/fuel-core/src/schema/dap.rs +++ b/crates/fuel-core/src/schema/dap.rs @@ -9,6 +9,7 @@ use crate::{ U64, }, }; +use anyhow::anyhow; use async_graphql::{ Context, Object, @@ -27,6 +28,8 @@ use fuel_core_types::{ Word, }, fuel_tx::{ + field::Policies, + policies::PolicyType, Buildable, ConsensusParameters, Executable, @@ -39,6 +42,7 @@ use fuel_core_types::{ IntoChecked, }, consts, + interpreter::InterpreterParams, state::DebugEval, Interpreter, InterpreterError, @@ -69,6 +73,11 @@ pub struct ConcreteStorage { params: ConsensusParameters, } +/// The gas price used for transactions in the debugger. It is set to 0 because +/// the debugger does not actually execute transactions, but only simulates +/// their execution. +const GAS_PRICE: u64 = 0; + impl ConcreteStorage { pub fn new(params: ConsensusParameters) -> Self { Self { @@ -112,8 +121,18 @@ impl ConcreteStorage { self.tx.insert(id.clone(), txs.to_owned()); }); - let mut vm = Interpreter::with_storage(vm_database, (&self.params).into()); - vm.transact(checked_tx).map_err(|e| anyhow::anyhow!(e))?; + let gas_costs = self.params.gas_costs(); + let fee_params = self.params.fee_params(); + + let ready_tx = checked_tx + .into_ready(GAS_PRICE, gas_costs, fee_params) + .map_err(|e| { + anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) + })?; + + let interpreter_params = InterpreterParams::new(GAS_PRICE, &self.params); + let mut vm = Interpreter::with_storage(vm_database, interpreter_params); + vm.transact(ready_tx).map_err(|e| anyhow::anyhow!(e))?; self.vm.insert(id.clone(), vm); self.db.insert(id.clone(), storage); @@ -143,8 +162,18 @@ impl ConcreteStorage { .into_checked_basic(vm_database.block_height()?, &self.params) .map_err(|e| anyhow::anyhow!("{:?}", e))?; - let mut vm = Interpreter::with_storage(vm_database, (&self.params).into()); - vm.transact(checked_tx).map_err(|e| anyhow::anyhow!(e))?; + let gas_costs = self.params.gas_costs(); + let fee_params = self.params.fee_params(); + + let ready_tx = checked_tx + .into_ready(GAS_PRICE, gas_costs, fee_params) + .map_err(|e| { + anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) + })?; + + let interpreter_params = InterpreterParams::new(GAS_PRICE, &self.params); + let mut vm = Interpreter::with_storage(vm_database, interpreter_params); + vm.transact(ready_tx).map_err(|e| anyhow::anyhow!(e))?; self.vm.insert(id.clone(), vm).ok_or_else(|| { io::Error::new(io::ErrorKind::NotFound, "The VM instance was not found") })?; @@ -189,9 +218,9 @@ impl ConcreteStorage { Default::default(), Default::default(), Default::default(), - Default::default(), ); tx.add_witness(vec![].into()); + tx.policies_mut().set(PolicyType::MaxFee, Some(0)); tx } } @@ -391,8 +420,10 @@ impl DapMutation { let db = locked.db.get(&id).ok_or("Invalid debugging session ID")?; + let params = locked.params.clone(); + let checked_tx = tx - .into_checked_basic(db.latest_height()?, &locked.params) + .into_checked_basic(db.latest_height()?, ¶ms) .map_err(|err| anyhow::anyhow!("{:?}", err))? .into(); @@ -401,9 +432,17 @@ impl DapMutation { .get_mut(&id) .ok_or_else(|| async_graphql::Error::new("VM not found"))?; + let gas_costs = params.gas_costs(); + let fee_params = params.fee_params(); + match checked_tx { CheckedTransaction::Script(script) => { - let state_ref = vm.transact(script).map_err(|err| { + let ready_tx = script + .into_ready(GAS_PRICE, gas_costs, fee_params) + .map_err(|e| { + anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) + })?; + let state_ref = vm.transact(ready_tx).map_err(|err| { async_graphql::Error::new(format!("Transaction failed: {err:?}")) })?; @@ -429,7 +468,12 @@ impl DapMutation { }) } CheckedTransaction::Create(create) => { - vm.deploy(create).map_err(|err| { + let ready_tx = create + .into_ready(GAS_PRICE, gas_costs, fee_params) + .map_err(|e| { + anyhow!("Failed to apply dynamic values to checked tx: {:?}", e) + })?; + vm.deploy(ready_tx).map_err(|err| { async_graphql::Error::new(format!( "Transaction deploy failed: {err:?}" )) diff --git a/crates/fuel-core/src/schema/tx/input.rs b/crates/fuel-core/src/schema/tx/input.rs index 70baa859ff..65599e4fdd 100644 --- a/crates/fuel-core/src/schema/tx/input.rs +++ b/crates/fuel-core/src/schema/tx/input.rs @@ -7,7 +7,6 @@ use crate::schema::scalars::{ Nonce, TxPointer, UtxoId, - U32, U64, }; use async_graphql::{ @@ -30,7 +29,6 @@ pub struct InputCoin { asset_id: AssetId, tx_pointer: TxPointer, witness_index: u8, - maturity: U32, predicate_gas_used: U64, predicate: HexString, predicate_data: HexString, @@ -62,10 +60,6 @@ impl InputCoin { self.witness_index } - async fn maturity(&self) -> U32 { - self.maturity - } - async fn predicate_gas_used(&self) -> U64 { self.predicate_gas_used } @@ -171,7 +165,6 @@ impl From<&fuel_tx::Input> for Input { asset_id, tx_pointer, witness_index, - maturity, .. }) => Input::Coin(InputCoin { utxo_id: UtxoId(*utxo_id), @@ -180,7 +173,6 @@ impl From<&fuel_tx::Input> for Input { asset_id: AssetId(*asset_id), tx_pointer: TxPointer(*tx_pointer), witness_index: *witness_index, - maturity: (*maturity).into(), predicate_gas_used: 0.into(), predicate: HexString(Default::default()), predicate_data: HexString(Default::default()), @@ -191,7 +183,6 @@ impl From<&fuel_tx::Input> for Input { amount, asset_id, tx_pointer, - maturity, predicate, predicate_data, predicate_gas_used, @@ -203,7 +194,6 @@ impl From<&fuel_tx::Input> for Input { asset_id: AssetId(*asset_id), tx_pointer: TxPointer(*tx_pointer), witness_index: Default::default(), - maturity: (*maturity).into(), predicate_gas_used: (*predicate_gas_used).into(), predicate: HexString(predicate.clone()), predicate_data: HexString(predicate_data.clone()), diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index a2723ab6ba..c3bf1edc6f 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -52,6 +52,7 @@ use fuel_core_types::{ Maturity, MintAmount, MintAssetId, + MintGasPrice, OutputContract, Outputs, Policies as PoliciesField, @@ -65,7 +66,6 @@ use fuel_core_types::{ Witnesses, }, policies::PolicyType, - Chargeable, Executable, TxId, }, @@ -307,8 +307,8 @@ pub struct Policies(fuel_tx::policies::Policies); #[Object] impl Policies { - async fn gas_price(&self) -> Option { - self.0.get(PolicyType::GasPrice).map(Into::into) + async fn tip(&self) -> Option { + self.0.get(PolicyType::Tip).map(Into::into) } async fn witness_limit(&self) -> Option { @@ -390,14 +390,6 @@ impl Transaction { } } - async fn gas_price(&self) -> Option { - match &self.0 { - fuel_tx::Transaction::Script(script) => Some(script.price().into()), - fuel_tx::Transaction::Create(create) => Some(create.price().into()), - fuel_tx::Transaction::Mint(_) => None, - } - } - async fn script_gas_limit(&self) -> Option { match &self.0 { fuel_tx::Transaction::Script(script) => { @@ -433,8 +425,7 @@ impl Transaction { async fn mint_gas_price(&self) -> Option { match &self.0 { fuel_tx::Transaction::Script(_) | fuel_tx::Transaction::Create(_) => None, - // TODO: We need to add a getter for the `gas_price` field in `Mint` transaction - fuel_tx::Transaction::Mint(_) => Some((0).into()), + fuel_tx::Transaction::Mint(mint) => Some((*mint.gas_price()).into()), } } diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index ba6303950b..7680776e79 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -84,11 +84,13 @@ impl fuel_core_producer::ports::Executor> for ExecutorAdapter { let Components { header_to_produce, transactions_source, + gas_price, gas_limit, } = component; self._execute_without_commit(ExecutionTypes::Production(Components { header_to_produce, transactions_source: OnceTransactionsSource::new(transactions_source), + gas_price, gas_limit, })) } diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index 4e0c884ea5..9dfa686ee2 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -100,7 +100,10 @@ impl Config { transaction_ttl: Duration::from_secs(60 * 100000000), ..fuel_core_txpool::Config::default() }, - block_producer: Default::default(), + block_producer: fuel_core_producer::Config { + gas_price: min_gas_price, + ..Default::default() + }, block_importer, #[cfg(feature = "relayer")] relayer: None, @@ -130,6 +133,16 @@ impl Config { tracing::warn!("The `ChainConfig` of `TxPool` was inconsistent"); self.txpool.chain_config = self.chain_conf.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/genesis.rs b/crates/fuel-core/src/service/genesis.rs index 3cc048f6b6..b3c2fcf5f1 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -356,7 +356,6 @@ fn create_coin_from_config(coin: &CoinConfig, generated_output_index: &mut u64) owner: coin.owner, amount: coin.amount, asset_id: coin.asset_id, - maturity: coin.maturity.unwrap_or_default(), tx_pointer: TxPointer::new( coin.tx_pointer_block_height.unwrap_or_default(), coin.tx_pointer_tx_idx.unwrap_or_default(), @@ -443,7 +442,6 @@ mod tests { let alice: Address = rng.gen(); let asset_id_alice: AssetId = rng.gen(); let alice_value = rng.gen(); - let alice_maturity: BlockHeight = rng.next_u32().into(); let alice_block_created: BlockHeight = rng.next_u32().into(); let alice_block_created_tx_idx = rng.gen(); let alice_tx_id = rng.gen(); @@ -464,7 +462,6 @@ mod tests { output_index: Some(alice_output_index), tx_pointer_block_height: Some(alice_block_created), tx_pointer_tx_idx: Some(alice_block_created_tx_idx), - maturity: Some(alice_maturity), owner: alice, amount: alice_value, asset_id: asset_id_alice, @@ -474,7 +471,6 @@ mod tests { output_index: None, tx_pointer_block_height: None, tx_pointer_tx_idx: None, - maturity: None, owner: bob, amount: bob_value, asset_id: asset_id_bob, @@ -509,14 +505,12 @@ mod tests { amount, asset_id, tx_pointer, - maturity, .. }] if utxo_id == alice_utxo_id && owner == alice && amount == alice_value && asset_id == asset_id_alice && tx_pointer.block_height() == alice_block_created - && maturity == alice_maturity, )); assert!(matches!( bob_coins.as_slice(), @@ -537,7 +531,7 @@ mod tests { let test_key: Bytes32 = rng.gen(); let test_value: Bytes32 = rng.gen(); - let state = vec![(test_key, test_value)]; + let state = vec![(test_key, test_value.to_vec())]; let salt: Salt = rng.gen(); let contract = Contract::from(op::ret(0x10).to_bytes().to_vec()); let root = contract.root(); @@ -576,7 +570,7 @@ mod tests { .expect("Expect a state entry to exist with test_key") .into_owned(); - assert_eq!(test_value, ret) + assert_eq!(test_value.to_vec(), ret.0) } #[tokio::test] @@ -677,7 +671,6 @@ mod tests { // set txpointer height > genesis height tx_pointer_block_height: Some(BlockHeight::from(11u32)), tx_pointer_tx_idx: Some(0), - maturity: None, owner: Default::default(), amount: 10, asset_id: Default::default(), diff --git a/crates/metrics/src/txpool_metrics.rs b/crates/metrics/src/txpool_metrics.rs index 14fc1d0b1b..6965f4ff98 100644 --- a/crates/metrics/src/txpool_metrics.rs +++ b/crates/metrics/src/txpool_metrics.rs @@ -10,7 +10,6 @@ use std::{ pub struct TxPoolMetrics { // Attaches each Metric to the Registry pub registry: Registry, - pub gas_price_histogram: Histogram, pub tx_size_histogram: Histogram, } @@ -18,26 +17,15 @@ impl Default for TxPoolMetrics { fn default() -> Self { let registry = Registry::default(); - let gas_prices = Vec::new(); - - let gas_price_histogram = Histogram::new(gas_prices.into_iter()); - let tx_sizes = Vec::new(); let tx_size_histogram = Histogram::new(tx_sizes.into_iter()); let mut metrics = TxPoolMetrics { registry, - gas_price_histogram, tx_size_histogram, }; - metrics.registry.register( - "Tx_Gas_Price_Histogram", - "A Histogram keeping track of all gas prices for each tx in the mempool", - metrics.gas_price_histogram.clone(), - ); - metrics.registry.register( "Tx_Size_Histogram", "A Histogram keeping track of the size of txs", diff --git a/crates/services/consensus_module/poa/src/service_test.rs b/crates/services/consensus_module/poa/src/service_test.rs index bd10b097e6..3ecd0292c2 100644 --- a/crates/services/consensus_module/poa/src/service_test.rs +++ b/crates/services/consensus_module/poa/src/service_test.rs @@ -252,7 +252,7 @@ impl MockTransactionPool { fn make_tx(rng: &mut StdRng) -> Script { TransactionBuilder::script(vec![], vec![]) - .gas_price(0) + .max_fee_limit(0) .script_gas_limit(rng.gen_range(1..TxParameters::DEFAULT.max_gas_per_tx)) .finalize_without_signature() } diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 535b306fdb..deac2fdb06 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -51,8 +51,10 @@ use fuel_core_types::{ fuel_tx::{ field::{ InputContract, + MaxFeeLimit, MintAmount, MintAssetId, + MintGasPrice, OutputContract, TxPointer as TxPointerField, }, @@ -98,9 +100,7 @@ use fuel_core_types::{ Checked, CheckedTransaction, Checks, - CreateCheckedMetadata, IntoChecked, - ScriptCheckedMetadata, }, interpreter::{ CheckedMetadata, @@ -312,6 +312,7 @@ where ExecutionTypes::Production(block) => ExecutionTypes::Production(Components { header_to_produce: block.header, transactions_source: OnceTransactionsSource::new(block.transactions), + gas_price: 0, gas_limit: u64::MAX, }), ExecutionTypes::Validation(block) => ExecutionTypes::Validation(block), @@ -347,6 +348,7 @@ where transactions_source: OnceTransactionsSource::new( component.transactions_source, ), + gas_price: component.gas_price, gas_limit: component.gas_limit, }; @@ -374,10 +376,12 @@ where // TODO: Make this module private after moving unit tests from `fuel-core` here. pub mod block_component { use super::*; + use fuel_core_types::fuel_tx::field::MintGasPrice; pub struct PartialBlockComponent<'a, TxSource> { pub empty_block: &'a mut PartialFuelBlock, pub transactions_source: TxSource, + pub gas_price: u64, pub gas_limit: u64, /// The private marker to allow creation of the type only by constructor. _marker: core::marker::PhantomData<()>, @@ -386,9 +390,16 @@ pub mod block_component { impl<'a> PartialBlockComponent<'a, OnceTransactionsSource> { pub fn from_partial_block(block: &'a mut PartialFuelBlock) -> Self { let transaction = core::mem::take(&mut block.transactions); + let gas_price = if let Some(Transaction::Mint(mint)) = transaction.last() { + *mint.gas_price() + } else { + 0 + }; + Self { empty_block: block, transactions_source: OnceTransactionsSource::new(transaction), + gas_price, gas_limit: u64::MAX, _marker: Default::default(), } @@ -399,12 +410,14 @@ pub mod block_component { pub fn from_component( block: &'a mut PartialFuelBlock, transactions_source: TxSource, + gas_price: u64, gas_limit: u64, ) -> Self { debug_assert!(block.transactions.is_empty()); PartialBlockComponent { empty_block: block, transactions_source, + gas_price, gas_limit, _marker: Default::default(), } @@ -442,6 +455,7 @@ where let component = PartialBlockComponent::from_component( &mut block, component.transactions_source, + component.gas_price, component.gas_limit, ); @@ -457,6 +471,7 @@ where let component = PartialBlockComponent::from_component( &mut block, component.transactions_source, + component.gas_price, component.gas_limit, ); @@ -544,6 +559,7 @@ where let (execution_kind, component) = block.split(); let block = component.empty_block; let source = component.transactions_source; + let gas_price = component.gas_price; let mut remaining_gas_limit = component.gas_limit; let block_height = *block.header.height(); @@ -567,6 +583,7 @@ where tx, &tx_id, &block.header, + gas_price, execution_data, execution_kind, &mut tx_st_transaction, @@ -642,8 +659,7 @@ where }, amount_to_mint, self.config.consensus_parameters.base_asset_id, - // TODO: Provide gas price https://github.com/FuelLabs/fuel-core/issues/1642 - 0, + gas_price, ); execute_transaction( @@ -711,6 +727,7 @@ where tx: MaybeCheckedTransaction, tx_id: &TxId, header: &PartialBlockHeader, + gas_price: Word, execution_data: &mut ExecutionData, execution_kind: ExecutionKind, tx_st_transaction: &mut StorageTransaction, @@ -740,6 +757,7 @@ where CheckedTransaction::Script(script) => self.execute_create_or_script( script, header, + gas_price, execution_data, tx_st_transaction, execution_kind, @@ -747,6 +765,7 @@ where CheckedTransaction::Create(create) => self.execute_create_or_script( create, header, + gas_price, execution_data, tx_st_transaction, execution_kind, @@ -754,6 +773,7 @@ where CheckedTransaction::Mint(mint) => self.execute_mint( mint, header, + gas_price, execution_data, tx_st_transaction, execution_kind, @@ -765,6 +785,7 @@ where &self, checked_mint: Checked, header: &PartialBlockHeader, + gas_price: Word, execution_data: &mut ExecutionData, block_st_transaction: &mut StorageTransaction, execution_kind: ExecutionKind, @@ -807,6 +828,9 @@ where if *mint.mint_amount() != execution_data.coinbase { return Err(ExecutorError::CoinbaseAmountMismatch) } + if *mint.gas_price() != gas_price { + return Err(ExecutorError::CoinbaseGasPriceMismatch) + } let block_height = *header.height(); @@ -820,26 +844,25 @@ where self.verify_input_state( block_st_transaction.as_ref(), inputs.as_mut_slice(), - block_height, header.da_height, )?; } - self.compute_inputs( - match execution_kind { - ExecutionKind::DryRun => { - ExecutionTypes::DryRun(inputs.as_mut_slice()) - } - ExecutionKind::Production => { - ExecutionTypes::Production(inputs.as_mut_slice()) - } - ExecutionKind::Validation => { - ExecutionTypes::Validation(inputs.as_slice()) - } - }, - coinbase_id, - block_st_transaction.as_mut(), - )?; + match execution_kind { + ExecutionKind::DryRun | ExecutionKind::Production => { + self.compute_inputs( + inputs.as_mut_slice(), + block_st_transaction.as_mut(), + )?; + } + ExecutionKind::Validation => { + self.validate_inputs_state( + inputs.as_mut_slice(), + coinbase_id, + block_st_transaction.as_mut(), + )?; + } + } let mut sub_block_db_commit = block_st_transaction.transaction(); @@ -867,21 +890,9 @@ where inputs.as_slice(), outputs.as_slice(), )?; - self.compute_not_utxo_outputs( - match execution_kind { - ExecutionKind::DryRun => ExecutionTypes::DryRun(( - outputs.as_mut_slice(), - inputs.as_slice(), - )), - ExecutionKind::Production => ExecutionTypes::Production(( - outputs.as_mut_slice(), - inputs.as_slice(), - )), - ExecutionKind::Validation => ExecutionTypes::Validation(( - outputs.as_slice(), - inputs.as_slice(), - )), - }, + self.compute_state_of_not_utxo_outputs( + outputs.as_mut_slice(), + inputs.as_slice(), coinbase_id, block_st_transaction.as_mut(), )?; @@ -928,16 +939,17 @@ where &self, mut checked_tx: Checked, header: &PartialBlockHeader, + gas_price: Word, execution_data: &mut ExecutionData, tx_st_transaction: &mut StorageTransaction, execution_kind: ExecutionKind, ) -> ExecutorResult where Tx: ExecutableTransaction + PartialEq + Cacheable + Send + Sync + 'static, - ::Metadata: Fee + CheckedMetadata + Clone + Send + Sync, + ::Metadata: CheckedMetadata + Clone + Send + Sync, { let tx_id = checked_tx.id(); - let max_fee = checked_tx.metadata().max_fee(); + let max_fee = checked_tx.transaction().max_fee_limit(); if self.options.utxo_validation { checked_tx = checked_tx @@ -955,7 +967,6 @@ where self.verify_input_state( tx_st_transaction.as_ref(), checked_tx.transaction().inputs(), - *header.height(), header.da_height, )?; // validate transaction signature @@ -965,6 +976,14 @@ where debug_assert!(checked_tx.checks().contains(Checks::Signatures)); } + if execution_kind == ExecutionKind::Validation { + self.validate_inputs_state( + checked_tx.transaction().inputs(), + tx_id, + tx_st_transaction.as_mut(), + )?; + } + // execute transaction // setup database view that only lives for the duration of vm execution let mut sub_block_db_commit = tx_st_transaction.transaction(); @@ -979,10 +998,18 @@ where let mut vm = Interpreter::with_storage( vm_db, - InterpreterParams::from(&self.config.consensus_parameters), + InterpreterParams::new(gas_price, &self.config.consensus_parameters), ); + + let gas_costs = &self.config.consensus_parameters.gas_costs; + let fee_params = &self.config.consensus_parameters.fee_params; + + let ready_tx = checked_tx + .clone() + .into_ready(gas_price, gas_costs, fee_params)?; + let vm_result: StateTransition<_> = vm - .transact(checked_tx.clone()) + .transact(ready_tx) .map_err(|error| ExecutorError::VmExecution { error: InterpreterError::Storage(anyhow::anyhow!(format!("{error:?}"))), transaction_id: tx_id, @@ -997,19 +1024,9 @@ where debug_assert_eq!(tx.id(&self.config.consensus_parameters.chain_id), tx_id); } - // TODO: We need to call this function before `vm.transact` but we can't do that because of - // `Checked` immutability requirements. So we do it here after its execution for now. - // But it should be fixed in the future. - // https://github.com/FuelLabs/fuel-vm/issues/651 - self.compute_inputs( - match execution_kind { - ExecutionKind::DryRun => ExecutionTypes::DryRun(tx.inputs_mut()), - ExecutionKind::Production => ExecutionTypes::Production(tx.inputs_mut()), - ExecutionKind::Validation => ExecutionTypes::Validation(tx.inputs()), - }, - tx_id, - tx_st_transaction.as_mut(), - )?; + // We always need to update inputs with storage state before execution, + // because VM zeroes malleable fields during the execution. + self.compute_inputs(tx.inputs_mut(), tx_st_transaction.as_mut())?; // only commit state changes if execution was a success if !reverted { @@ -1017,22 +1034,8 @@ where } // update block commitment - let (used_gas, tx_fee) = self.total_fee_paid(&tx, max_fee, &receipts)?; - - // Check or set the executed transaction. - match execution_kind { - ExecutionKind::Validation => { - // ensure tx matches vm output exactly - if &tx != checked_tx.transaction() { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - } - } - ExecutionKind::DryRun | ExecutionKind::Production => { - // malleate the block with the resultant tx from the vm - } - } + let (used_gas, tx_fee) = + self.total_fee_paid(&tx, max_fee, &receipts, gas_price)?; // change the spent status of the tx inputs self.spend_input_utxos( @@ -1051,26 +1054,25 @@ where tx.inputs(), tx.outputs(), )?; - // TODO: Inputs, in most cases, are heavier than outputs, so cloning them, but we - // need to avoid cloning in the future. - let mut outputs = tx.outputs().clone(); - self.compute_not_utxo_outputs( - match execution_kind { - ExecutionKind::DryRun => { - ExecutionTypes::DryRun((&mut outputs, tx.inputs())) - } - ExecutionKind::Production => { - ExecutionTypes::Production((&mut outputs, tx.inputs())) - } - ExecutionKind::Validation => { - ExecutionTypes::Validation((&outputs, tx.inputs())) - } - }, + + // We always need to update outputs with storage state after execution. + let mut outputs = core::mem::take(tx.outputs_mut()); + self.compute_state_of_not_utxo_outputs( + &mut outputs, + tx.inputs(), tx_id, tx_st_transaction.as_mut(), )?; *tx.outputs_mut() = outputs; + // The validator ensures that the generated transaction by him is the same as provided by the block producer. + if execution_kind == ExecutionKind::Validation && &tx != checked_tx.transaction() + { + return Err(ExecutorError::InvalidTransactionOutcome { + transaction_id: tx_id, + }) + } + let final_tx = tx.into(); // Store tx into the block db transaction @@ -1116,7 +1118,6 @@ where &self, db: &D, inputs: &[Input], - block_height: BlockHeight, block_da_height: DaBlockHeight, ) -> ExecutorResult<()> { for input in inputs { @@ -1124,18 +1125,6 @@ where Input::CoinSigned(CoinSigned { utxo_id, .. }) | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => { if let Some(coin) = db.storage::().get(utxo_id)? { - let coin_mature_height = coin - .tx_pointer() - .block_height() - .saturating_add(**coin.maturity()) - .into(); - if block_height < coin_mature_height { - return Err(TransactionValidityError::CoinHasNotMatured( - *utxo_id, - ) - .into()) - } - if !coin .matches_input(input) .expect("The input is a coin above") @@ -1214,7 +1203,6 @@ where owner, amount, asset_id, - maturity, .. }) | Input::CoinPredicate(CoinPredicate { @@ -1222,7 +1210,6 @@ where owner, amount, asset_id, - maturity, .. }) => { // prune utxo from db @@ -1235,7 +1222,7 @@ where // If the coin is not found in the database, it means that it was // already spent or `utxo_validation` is `false`. self.get_coin_or_default( - db, *utxo_id, *owner, *amount, *asset_id, *maturity, + db, *utxo_id, *owner, *amount, *asset_id, ) })?; @@ -1281,6 +1268,7 @@ where tx: &Tx, max_fee: Word, receipts: &[Receipt], + gas_price: Word, ) -> ExecutorResult<(Word, Word)> { let mut used_gas = 0; for r in receipts { @@ -1295,6 +1283,7 @@ where self.config.consensus_parameters.gas_costs(), self.config.consensus_parameters.fee_params(), used_gas, + gas_price, ) .ok_or(ExecutorError::FeeOverflow)?; // if there's no script result (i.e. create) then fee == base amount @@ -1309,123 +1298,113 @@ where /// Computes all zeroed or variable inputs. /// In production mode, updates the inputs with computed values. /// In validation mode, compares the inputs with computed inputs. - fn compute_inputs( + fn compute_inputs(&self, inputs: &mut [Input], db: &mut D) -> ExecutorResult<()> { + for input in inputs { + match input { + Input::CoinSigned(CoinSigned { + tx_pointer, + utxo_id, + owner, + amount, + asset_id, + .. + }) + | Input::CoinPredicate(CoinPredicate { + tx_pointer, + utxo_id, + owner, + amount, + asset_id, + .. + }) => { + let coin = self + .get_coin_or_default(db, *utxo_id, *owner, *amount, *asset_id)?; + *tx_pointer = *coin.tx_pointer(); + } + Input::Contract(Contract { + ref mut utxo_id, + ref mut balance_root, + ref mut state_root, + ref mut tx_pointer, + ref contract_id, + .. + }) => { + let mut contract = ContractRef::new(&mut *db, *contract_id); + let utxo_info = + contract.validated_utxo(self.options.utxo_validation)?; + *utxo_id = *utxo_info.utxo_id(); + *tx_pointer = utxo_info.tx_pointer(); + *balance_root = contract.balance_root()?; + *state_root = contract.state_root()?; + } + _ => {} + } + } + Ok(()) + } + + fn validate_inputs_state( &self, - inputs: ExecutionTypes<&mut [Input], &[Input]>, + inputs: &[Input], tx_id: TxId, db: &mut D, ) -> ExecutorResult<()> { - match inputs { - ExecutionTypes::DryRun(inputs) | ExecutionTypes::Production(inputs) => { - for input in inputs { - match input { - Input::CoinSigned(CoinSigned { - tx_pointer, - utxo_id, - owner, - amount, - asset_id, - maturity, - .. + for input in inputs { + match input { + Input::CoinSigned(CoinSigned { + tx_pointer, + utxo_id, + owner, + amount, + asset_id, + .. + }) + | Input::CoinPredicate(CoinPredicate { + tx_pointer, + utxo_id, + owner, + amount, + asset_id, + .. + }) => { + let coin = self + .get_coin_or_default(db, *utxo_id, *owner, *amount, *asset_id)?; + if tx_pointer != coin.tx_pointer() { + return Err(ExecutorError::InvalidTransactionOutcome { + transaction_id: tx_id, }) - | Input::CoinPredicate(CoinPredicate { - tx_pointer, - utxo_id, - owner, - amount, - asset_id, - maturity, - .. - }) => { - let coin = self.get_coin_or_default( - db, *utxo_id, *owner, *amount, *asset_id, *maturity, - )?; - *tx_pointer = *coin.tx_pointer(); - } - Input::Contract(Contract { - ref mut utxo_id, - ref mut balance_root, - ref mut state_root, - ref mut tx_pointer, - ref contract_id, - .. - }) => { - let mut contract = ContractRef::new(&mut *db, *contract_id); - let utxo_info = - contract.validated_utxo(self.options.utxo_validation)?; - *utxo_id = *utxo_info.utxo_id(); - *tx_pointer = utxo_info.tx_pointer(); - *balance_root = contract.balance_root()?; - *state_root = contract.state_root()?; - } - _ => {} } } - } - // Needed to convince the compiler that tx is taken by ref here - ExecutionTypes::Validation(inputs) => { - for input in inputs { - match input { - Input::CoinSigned(CoinSigned { - tx_pointer, - utxo_id, - owner, - amount, - asset_id, - maturity, - .. + Input::Contract(Contract { + utxo_id, + balance_root, + state_root, + contract_id, + tx_pointer, + .. + }) => { + let mut contract = ContractRef::new(&mut *db, *contract_id); + let provided_info = + ContractUtxoInfo::V1((*utxo_id, *tx_pointer).into()); + if provided_info + != contract.validated_utxo(self.options.utxo_validation)? + { + return Err(ExecutorError::InvalidTransactionOutcome { + transaction_id: tx_id, + }) + } + if balance_root != &contract.balance_root()? { + return Err(ExecutorError::InvalidTransactionOutcome { + transaction_id: tx_id, + }) + } + if state_root != &contract.state_root()? { + return Err(ExecutorError::InvalidTransactionOutcome { + transaction_id: tx_id, }) - | Input::CoinPredicate(CoinPredicate { - tx_pointer, - utxo_id, - owner, - amount, - asset_id, - maturity, - .. - }) => { - let coin = self.get_coin_or_default( - db, *utxo_id, *owner, *amount, *asset_id, *maturity, - )?; - if tx_pointer != coin.tx_pointer() { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - } - } - Input::Contract(Contract { - utxo_id, - balance_root, - state_root, - contract_id, - tx_pointer, - .. - }) => { - let mut contract = ContractRef::new(&mut *db, *contract_id); - let provided_info = - ContractUtxoInfo::V1((*utxo_id, *tx_pointer).into()); - if provided_info - != contract - .validated_utxo(self.options.utxo_validation)? - { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - } - if balance_root != &contract.balance_root()? { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - } - if state_root != &contract.state_root()? { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - } - } - _ => {} } } + _ => {} } } Ok(()) @@ -1436,62 +1415,29 @@ where /// Computes all zeroed or variable outputs. /// In production mode, updates the outputs with computed values. /// In validation mode, compares the outputs with computed inputs. - fn compute_not_utxo_outputs( + fn compute_state_of_not_utxo_outputs( &self, - tx: ExecutionTypes<(&mut [Output], &[Input]), (&[Output], &[Input])>, + outputs: &mut [Output], + inputs: &[Input], tx_id: TxId, db: &mut D, ) -> ExecutorResult<()> { - match tx { - ExecutionTypes::DryRun(tx) | ExecutionTypes::Production(tx) => { - for output in tx.0.iter_mut() { - if let Output::Contract(contract_output) = output { - let contract_id = - if let Some(Input::Contract(Contract { - contract_id, .. - })) = tx.1.get(contract_output.input_index as usize) - { - contract_id - } else { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - }; - - let mut contract = ContractRef::new(&mut *db, *contract_id); - contract_output.balance_root = contract.balance_root()?; - contract_output.state_root = contract.state_root()?; - } - } - } - ExecutionTypes::Validation(tx) => { - for output in tx.0 { - if let Output::Contract(contract_output) = output { - let contract_id = - if let Some(Input::Contract(Contract { - contract_id, .. - })) = tx.1.get(contract_output.input_index as usize) - { - contract_id - } else { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - }; - - let mut contract = ContractRef::new(&mut *db, *contract_id); - if contract_output.balance_root != contract.balance_root()? { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - } - if contract_output.state_root != contract.state_root()? { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - } - } - } + for output in outputs { + if let Output::Contract(contract_output) = output { + let contract_id = + if let Some(Input::Contract(Contract { contract_id, .. })) = + inputs.get(contract_output.input_index as usize) + { + contract_id + } else { + return Err(ExecutorError::InvalidTransactionOutcome { + transaction_id: tx_id, + }) + }; + + let mut contract = ContractRef::new(&mut *db, *contract_id); + contract_output.balance_root = contract.balance_root()?; + contract_output.state_root = contract.state_root()?; } } Ok(()) @@ -1505,7 +1451,6 @@ where owner: Address, amount: u64, asset_id: AssetId, - maturity: BlockHeight, ) -> ExecutorResult { if self.options.utxo_validation { db.storage::() @@ -1520,7 +1465,6 @@ where owner, amount, asset_id, - maturity, tx_pointer: Default::default(), } .into(); @@ -1656,7 +1600,6 @@ where owner: *to, amount: *amount, asset_id: *asset_id, - maturity: 0u32.into(), tx_pointer: TxPointer::new(block_height, execution_data.tx_count), } .into(); @@ -1672,29 +1615,3 @@ where Ok(()) } } - -trait Fee { - fn max_fee(&self) -> Word; - - fn min_fee(&self) -> Word; -} - -impl Fee for ScriptCheckedMetadata { - fn max_fee(&self) -> Word { - self.fee.max_fee() - } - - fn min_fee(&self) -> Word { - self.fee.min_fee() - } -} - -impl Fee for CreateCheckedMetadata { - fn max_fee(&self) -> Word { - self.fee.max_fee() - } - - fn min_fee(&self) -> Word { - self.fee.min_fee() - } -} diff --git a/crates/services/executor/src/ports.rs b/crates/services/executor/src/ports.rs index 17e78c3b25..f831d5031d 100644 --- a/crates/services/executor/src/ports.rs +++ b/crates/services/executor/src/ports.rs @@ -17,6 +17,8 @@ use fuel_core_storage::{ StorageBatchMutate, StorageMutate, StorageRead, + StorageSize, + StorageWrite, }; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, @@ -73,13 +75,17 @@ pub trait ExecutorDatabaseTrait: StorageInspect + StorageMutate + StorageMutate - + MerkleRootStorage + StorageMutate + StorageMutate + StorageMutate - + StorageMutate + + StorageWrite + + StorageSize + + StorageRead + + StorageWrite + + StorageSize + + StorageRead + + MerkleRootStorage + StorageMutate - + StorageRead + StorageMutate + MerkleRootStorage + StorageBatchMutate diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index fdef5854d5..a64af18ab4 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -110,6 +110,8 @@ where 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_limit: max_gas, }; @@ -203,6 +205,8 @@ 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_limit: u64::MAX, }; diff --git a/crates/services/producer/src/config.rs b/crates/services/producer/src/config.rs index 71efd451a1..4345b84b23 100644 --- a/crates/services/producer/src/config.rs +++ b/crates/services/producer/src/config.rs @@ -4,5 +4,6 @@ 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/txpool/src/containers/dependency.rs b/crates/services/txpool/src/containers/dependency.rs index 33ea6a06c2..165d65355a 100644 --- a/crates/services/txpool/src/containers/dependency.rs +++ b/crates/services/txpool/src/containers/dependency.rs @@ -72,7 +72,7 @@ pub struct ContractState { /// origin is needed for child to parent rel, in case when contract is in dependency this is how we make a chain. origin: Option, /// gas_price. We can probably derive this from Tx - gas_price: GasPrice, + tip: Word, } impl ContractState { @@ -87,7 +87,7 @@ impl ContractState { #[derive(Debug, Clone)] pub struct MessageState { spent_by: TxId, - gas_price: GasPrice, + tip: Word, } impl Dependency { @@ -289,43 +289,39 @@ impl Dependency { .get(spend_by) .expect("Tx should be always present in txpool"); // compare if tx has better price - if txpool_tx.price() > tx.price() { + if txpool_tx.tip() > tx.tip() { return Err(Error::NotInsertedCollision( *spend_by, *utxo_id, )) - } else { - if state.is_in_database() { - // this means it is loaded from db. Get tx to compare output. - if self.utxo_validation { - let coin = db - .utxo(utxo_id) - .map_err(|e| { - Error::Database(format!("{:?}", e)) - })? - .ok_or( - Error::NotInsertedInputUtxoIdNotDoesNotExist( - *utxo_id, - ), - )?; - if !coin - .matches_input(input) - .expect("The input is coin above") - { - return Err(Error::NotInsertedIoCoinMismatch) - } + } else if state.is_in_database() { + // this means it is loaded from db. Get tx to compare output. + if self.utxo_validation { + let coin = db + .utxo(utxo_id) + .map_err(|e| Error::Database(format!("{:?}", e)))? + .ok_or( + Error::NotInsertedInputUtxoIdNotDoesNotExist( + *utxo_id, + ), + )?; + if !coin + .matches_input(input) + .expect("The input is coin above") + { + return Err(Error::NotInsertedIoCoinMismatch) } - } else { - // tx output is in pool - let output_tx = txs.get(utxo_id.tx_id()).unwrap(); - let output = &output_tx.outputs() - [utxo_id.output_index() as usize]; - Self::check_if_coin_input_can_spend_output( - output, input, false, - )?; - }; - - collided.push(*spend_by); + } + } else { + // tx output is in pool + let output_tx = txs.get(utxo_id.tx_id()).unwrap(); + let output = + &output_tx.outputs()[utxo_id.output_index() as usize]; + Self::check_if_coin_input_can_spend_output( + output, input, false, + )?; } + + collided.push(*spend_by); } // if coin is not spend, it will be spend later down the line } else { @@ -389,8 +385,7 @@ impl Dependency { } if let Some(state) = self.messages.get(nonce) { - // some other is already attempting to spend this message, compare gas price - if state.gas_price >= tx.price() { + if state.tip >= tx.tip() { return Err(Error::NotInsertedCollisionMessageId( state.spent_by, *nonce, @@ -403,7 +398,7 @@ impl Dependency { *nonce, MessageState { spent_by: tx.id(), - gas_price: tx.price(), + tip: tx.tip(), }, ); } @@ -411,7 +406,7 @@ impl Dependency { // Does contract exist. We don't need to do any check here other then if contract_id exist or not. if let Some(state) = self.contracts.get(contract_id) { // check if contract is created after this transaction. - if tx.price() > state.gas_price { + if tx.tip() > state.tip { return Err(Error::NotInsertedContractPricedLower( *contract_id, )) @@ -439,7 +434,7 @@ impl Dependency { used_by: HashSet::new(), depth: 0, origin: None, // there is no owner if contract is in db - gas_price: GasPrice::MAX, + tip: Word::MAX, }) .used_by .insert(tx.id()); @@ -459,7 +454,7 @@ impl Dependency { return Err(Error::NotInsertedContractIdAlreadyTaken(*contract_id)) } // check who is priced more - if contract.gas_price > tx.price() { + if contract.tip > tx.tip() { // new tx is priced less then current tx return Err(Error::NotInsertedCollisionContractId(*contract_id)) } @@ -562,7 +557,7 @@ impl Dependency { depth: max_depth, used_by: HashSet::new(), origin: Some(utxo_id), - gas_price: tx.price(), + tip: tx.tip(), }, ); } @@ -708,7 +703,6 @@ mod tests { UtxoId::default(), Address::default(), 10, - AssetId::default(), Default::default(), Default::default(), Default::default(), diff --git a/crates/services/txpool/src/containers/price_sort.rs b/crates/services/txpool/src/containers/price_sort.rs index c82bfac3f0..cc061d47f9 100644 --- a/crates/services/txpool/src/containers/price_sort.rs +++ b/crates/services/txpool/src/containers/price_sort.rs @@ -9,26 +9,26 @@ use crate::{ use std::cmp; /// all transactions sorted by min/max price -pub type PriceSort = Sort; +pub type TipSort = Sort; #[derive(Clone, Debug)] -pub struct PriceSortKey { - price: GasPrice, +pub struct TipSortKey { + tip: Word, tx_id: TxId, } -impl SortableKey for PriceSortKey { +impl SortableKey for TipSortKey { type Value = GasPrice; fn new(info: &TxInfo) -> Self { Self { - price: info.tx().price(), + tip: info.tx().tip(), tx_id: info.tx().id(), } } fn value(&self) -> &Self::Value { - &self.price + &self.tip } fn tx_id(&self) -> &TxId { @@ -36,23 +36,23 @@ impl SortableKey for PriceSortKey { } } -impl PartialEq for PriceSortKey { +impl PartialEq for TipSortKey { fn eq(&self, other: &Self) -> bool { self.tx_id == other.tx_id } } -impl Eq for PriceSortKey {} +impl Eq for TipSortKey {} -impl PartialOrd for PriceSortKey { +impl PartialOrd for TipSortKey { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for PriceSortKey { +impl Ord for TipSortKey { fn cmp(&self, other: &Self) -> cmp::Ordering { - let cmp = self.price.cmp(&other.price); + let cmp = self.tip.cmp(&other.tip); if cmp == cmp::Ordering::Equal { return self.tx_id.cmp(&other.tx_id) } diff --git a/crates/services/txpool/src/service/test_helpers.rs b/crates/services/txpool/src/service/test_helpers.rs index a1a44d7bed..1be18b1f32 100644 --- a/crates/services/txpool/src/service/test_helpers.rs +++ b/crates/services/txpool/src/service/test_helpers.rs @@ -46,10 +46,11 @@ impl TestContext { &self.service } - pub fn setup_script_tx(&self, gas_price: Word) -> Transaction { + pub fn setup_script_tx(&self, tip: Word) -> Transaction { let (_, gas_coin) = self.setup_coin(); let mut tx = TransactionBuilder::script(vec![], vec![]) - .gas_price(gas_price) + .max_fee_limit(tip) + .tip(tip) .script_gas_limit(1000) .add_input(gas_coin) .finalize_as_transaction(); @@ -171,10 +172,11 @@ impl TestContextBuilder { self.p2p = Some(p2p) } - pub fn setup_script_tx(&mut self, gas_price: Word) -> Transaction { + pub fn setup_script_tx(&mut self, tip: Word) -> Transaction { let (_, gas_coin) = self.setup_coin(); TransactionBuilder::script(vec![], vec![]) - .gas_price(gas_price) + .tip(tip) + .max_fee_limit(tip) .script_gas_limit(1000) .add_input(gas_coin) .finalize_as_transaction() diff --git a/crates/services/txpool/src/service/tests.rs b/crates/services/txpool/src/service/tests.rs index 85da7a46c7..05b77f7299 100644 --- a/crates/services/txpool/src/service/tests.rs +++ b/crates/services/txpool/src/service/tests.rs @@ -206,22 +206,25 @@ async fn simple_insert_removal_subscription() { let out = service.shared.insert(vec![tx1.clone(), tx2.clone()]).await; - if out[0].is_ok() { - assert_eq!( - new_tx_notification.try_recv(), - Ok(tx1.cached_id().unwrap()), - "First added should be tx1" - ); - let update = tx1_subscribe_updates.next().await.unwrap(); - assert!( - matches!( - update, - TxStatusMessage::Status(TransactionStatus::Submitted { .. }) - ), - "First message in tx1 stream should be Submitted" - ); - } else { - panic!("Tx1 should be OK, got err"); + match &out[0] { + Ok(_) => { + assert_eq!( + new_tx_notification.try_recv(), + Ok(tx1.cached_id().unwrap()), + "First added should be tx1" + ); + let update = tx1_subscribe_updates.next().await.unwrap(); + assert!( + matches!( + update, + TxStatusMessage::Status(TransactionStatus::Submitted { .. }) + ), + "First message in tx1 stream should be Submitted" + ); + } + Err(err) => { + panic!("Tx1 should be OK, got err, {:?}", err) + } } if out[1].is_ok() { diff --git a/crates/services/txpool/src/test_helpers.rs b/crates/services/txpool/src/test_helpers.rs index 1f34203292..638065b533 100644 --- a/crates/services/txpool/src/test_helpers.rs +++ b/crates/services/txpool/src/test_helpers.rs @@ -117,7 +117,6 @@ impl TextContext { asset_id, Default::default(), Default::default(), - Default::default(), code, vec![], ) @@ -164,7 +163,6 @@ pub(crate) fn random_predicate( asset_id, Default::default(), Default::default(), - Default::default(), predicate_code, vec![], ) diff --git a/crates/services/txpool/src/transaction_selector.rs b/crates/services/txpool/src/transaction_selector.rs index dec49e0182..a148df39d5 100644 --- a/crates/services/txpool/src/transaction_selector.rs +++ b/crates/services/txpool/src/transaction_selector.rs @@ -69,7 +69,7 @@ mod tests { #[derive(Debug, Clone, Copy, PartialEq)] struct TxGas { - pub price: u64, + pub tip: u64, pub limit: u64, } @@ -90,7 +90,7 @@ mod tests { vec![op::ret(RegId::ONE)].into_iter().collect(), vec![], ) - .gas_price(tx_gas.price) + .tip(tx_gas.tip) .script_gas_limit(tx_gas.limit) .add_unsigned_coin_input( SecretKey::random(&mut rng), @@ -98,7 +98,6 @@ mod tests { 1_000_000, Default::default(), Default::default(), - Default::default(), ) .add_output(Output::Change { to: Default::default(), @@ -113,13 +112,13 @@ mod tests { }) .map(Arc::new) .collect::>(); - txs.sort_by_key(|a| core::cmp::Reverse(a.price())); + txs.sort_by_key(|a| core::cmp::Reverse(a.tip())); select_transactions(txs.into_iter(), block_gas_limit) .into_iter() .map(|tx| TxGas { limit: tx.script_gas_limit().unwrap_or_default(), - price: tx.price(), + tip: tx.tip(), }) .collect() } @@ -133,34 +132,34 @@ mod tests { #[rstest::rstest] #[test] #[case(999, vec![])] - #[case(1000, vec![TxGas { price: 5, limit: 1000 }])] - #[case(2500, vec![TxGas { price: 5, limit: 1000 }, TxGas { price: 2, limit: 1000 }])] + #[case(1000, vec![TxGas { tip: 5, limit: 1000 }])] + #[case(2500, vec![TxGas { tip: 5, limit: 1000 }, TxGas { tip: 2, limit: 1000 }])] #[case(4000, vec![ - TxGas { price: 5, limit: 1000 }, - TxGas { price: 4, limit: 3000 } + TxGas { tip: 5, limit: 1000 }, + TxGas { tip: 4, limit: 3000 } ])] #[case(5000, vec![ - TxGas { price: 5, limit: 1000 }, - TxGas { price: 4, limit: 3000 }, - TxGas { price: 2, limit: 1000 }]) + TxGas { tip: 5, limit: 1000 }, + TxGas { tip: 4, limit: 3000 }, + TxGas { tip: 2, limit: 1000 }]) ] #[case(6_000, vec![ - TxGas { price: 5, limit: 1000 }, - TxGas { price: 4, limit: 3000 }, - TxGas { price: 3, limit: 2000 } + TxGas { tip: 5, limit: 1000 }, + TxGas { tip: 4, limit: 3000 }, + TxGas { tip: 3, limit: 2000 } ])] #[case(7_000, vec![ - TxGas { price: 5, limit: 1000 }, - TxGas { price: 4, limit: 3000 }, - TxGas { price: 3, limit: 2000 }, - TxGas { price: 2, limit: 1000 } + TxGas { tip: 5, limit: 1000 }, + TxGas { tip: 4, limit: 3000 }, + TxGas { tip: 3, limit: 2000 }, + TxGas { tip: 2, limit: 1000 } ])] #[case(8_000, vec![ - TxGas { price: 5, limit: 1000 }, - TxGas { price: 4, limit: 3000 }, - TxGas { price: 3, limit: 2000 }, - TxGas { price: 2, limit: 1000 }, - TxGas { price: 1, limit: 1000 } + TxGas { tip: 5, limit: 1000 }, + TxGas { tip: 4, limit: 3000 }, + TxGas { tip: 3, limit: 2000 }, + TxGas { tip: 2, limit: 1000 }, + TxGas { tip: 1, limit: 1000 } ])] fn selector_prefers_highest_gas_txs_and_sorts( #[case] selection_limit: u64, @@ -168,11 +167,11 @@ mod tests { ) { #[rustfmt::skip] let original = [ - TxGas { price: 3, limit: 2000 }, - TxGas { price: 1, limit: 1000 }, - TxGas { price: 4, limit: 3000 }, - TxGas { price: 5, limit: 1000 }, - TxGas { price: 2, limit: 1000 }, + TxGas { tip: 3, limit: 2000 }, + TxGas { tip: 1, limit: 1000 }, + TxGas { tip: 4, limit: 3000 }, + TxGas { tip: 5, limit: 1000 }, + TxGas { tip: 2, limit: 1000 }, ]; let selected = make_txs_and_select(&original, selection_limit); @@ -186,11 +185,11 @@ mod tests { fn selector_doesnt_exceed_max_gas_per_block() { #[rustfmt::skip] let original = [ - TxGas { price: 3, limit: 2000 }, - TxGas { price: 1, limit: 1000 }, - TxGas { price: 4, limit: 3000 }, - TxGas { price: 5, limit: 1000 }, - TxGas { price: 2, limit: 1000 }, + TxGas { tip: 3, limit: 2000 }, + TxGas { tip: 1, limit: 1000 }, + TxGas { tip: 4, limit: 3000 }, + TxGas { tip: 5, limit: 1000 }, + TxGas { tip: 2, limit: 1000 }, ]; for k in 0..original.len() { diff --git a/crates/services/txpool/src/txpool.rs b/crates/services/txpool/src/txpool.rs index 61d140ef8d..2139869a83 100644 --- a/crates/services/txpool/src/txpool.rs +++ b/crates/services/txpool/src/txpool.rs @@ -1,7 +1,7 @@ use crate::{ containers::{ dependency::Dependency, - price_sort::PriceSort, + price_sort::TipSort, time_sort::TimeSort, }, ports::TxPoolDb, @@ -12,10 +12,7 @@ use crate::{ TxInfo, }; use fuel_core_types::{ - fuel_tx::{ - Chargeable, - Transaction, - }, + fuel_tx::Transaction, fuel_types::BlockHeight, fuel_vm::{ checked_transaction::{ @@ -57,7 +54,7 @@ use tokio_rayon::AsyncRayonHandle; #[derive(Debug, Clone)] pub struct TxPool { by_hash: HashMap, - by_gas_price: PriceSort, + by_tip: TipSort, by_time: TimeSort, by_dependency: Dependency, config: Config, @@ -70,7 +67,7 @@ impl TxPool { Self { by_hash: HashMap::new(), - by_gas_price: PriceSort::default(), + by_tip: TipSort::default(), by_time: TimeSort::default(), by_dependency: Dependency::new(max_depth, config.utxo_validation), config, @@ -93,11 +90,7 @@ impl TxPool { /// Return all sorted transactions that are includable in next block. pub fn sorted_includable(&self) -> impl Iterator + '_ { - self.by_gas_price - .sort - .iter() - .rev() - .map(|(_, tx)| tx.clone()) + self.by_tip.sort.iter().rev().map(|(_, tx)| tx.clone()) } pub fn remove_inner(&mut self, tx: &ArcPoolTx) -> Vec { @@ -123,7 +116,7 @@ impl TxPool { let info = self.by_hash.remove(tx_id); if let Some(info) = &info { self.by_time.remove(info); - self.by_gas_price.remove(info); + self.by_tip.remove(info); } info @@ -167,7 +160,7 @@ impl TxPool { } let mut list: Vec<_> = seen.into_values().collect(); // sort from high to low price - list.sort_by_key(|tx| Reverse(tx.price())); + list.sort_by_key(|tx| Reverse(tx.tip())); list } @@ -304,16 +297,12 @@ where if self.by_hash.len() >= self.config.max_tx { max_limit_hit = true; // limit is hit, check if we can push out lowest priced tx - let lowest_price = self.by_gas_price.lowest_value().unwrap_or_default(); - if lowest_price >= tx.price() { + let lowest_tip = self.by_tip.lowest_value().unwrap_or_default(); + if lowest_tip >= tx.tip() { return Err(Error::NotInsertedLimitHit) } } if self.config.metrics { - txpool_metrics() - .gas_price_histogram - .observe(tx.price() as f64); - txpool_metrics() .tx_size_histogram .observe(tx.metered_bytes_size() as f64); @@ -322,7 +311,7 @@ where let rem = self.by_dependency.insert(&self.by_hash, view, &tx)?; let info = TxInfo::new(tx.clone()); let submitted_time = info.submitted_time(); - self.by_gas_price.insert(&info); + self.by_tip.insert(&info); self.by_time.insert(&info); self.by_hash.insert(tx.id(), info); @@ -330,7 +319,7 @@ where let removed = if rem.is_empty() { if max_limit_hit { // remove last tx from sort - let rem_tx = self.by_gas_price.lowest_tx().unwrap(); // safe to unwrap limit is hit + let rem_tx = self.by_tip.lowest_tx().unwrap(); // safe to unwrap limit is hit self.remove_inner(&rem_tx); vec![rem_tx] } else { @@ -419,8 +408,6 @@ pub async fn check_single_tx( return Err(Error::NotSupportedTransactionType) } - verify_tx_min_gas_price(&tx, config)?; - let tx: Checked = if config.utxo_validation { let consensus_params = &config.chain_config.consensus_parameters; @@ -441,25 +428,33 @@ 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)?; + Ok(tx) } -fn verify_tx_min_gas_price(tx: &Transaction, config: &Config) -> Result<(), Error> { - let price = match tx { - Transaction::Script(script) => script.price(), - Transaction::Create(create) => create.price(), - Transaction::Mint(_) => return Err(Error::NotSupportedTransactionType), +fn verify_tx_min_gas_price( + tx: Checked, + config: &Config, +) -> 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(); + CheckedTransaction::Script(checked) + } + CheckedTransaction::Create(create) => { + let read = create.into_ready(min_gas_price, gas_costs, fee_parameters)?; + let (_, checked) = read.decompose(); + CheckedTransaction::Create(checked) + } + CheckedTransaction::Mint(_) => return Err(Error::MintIsDisallowed), }; - if config.metrics { - // Gas Price metrics are recorded here to avoid double matching for - // every single transaction, but also means metrics aren't collected on gas - // price if there is no minimum gas price - txpool_metrics().gas_price_histogram.observe(price as f64); - } - if price < config.min_gas_price { - return Err(Error::NotInsertedGasPriceTooLow) - } - Ok(()) + Ok(read.into()) } pub struct TokioWithRayon; diff --git a/crates/services/txpool/src/txpool/tests.rs b/crates/services/txpool/src/txpool/tests.rs index e3d73e4ed1..f601aa9a6c 100644 --- a/crates/services/txpool/src/txpool/tests.rs +++ b/crates/services/txpool/src/txpool/tests.rs @@ -32,7 +32,10 @@ use fuel_core_types::{ UtxoId, }, fuel_types::ChainId, - fuel_vm::checked_transaction::Checked, + fuel_vm::checked_transaction::{ + CheckError, + Checked, + }, }; use std::{ cmp::Reverse, @@ -82,7 +85,8 @@ async fn insert_simple_tx_dependency_chain_succeeds() { let (_, gas_coin) = context.setup_coin(); let (output, unset_input) = context.create_output_and_input(1); let tx1 = TransactionBuilder::script(vec![], vec![]) - .gas_price(1) + .tip(1) + .max_fee_limit(1) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .add_output(output) @@ -91,7 +95,8 @@ async fn insert_simple_tx_dependency_chain_succeeds() { let (_, gas_coin) = context.setup_coin(); let input = unset_input.into_input(UtxoId::new(tx1.id(&Default::default()), 0)); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(1) + .tip(1) + .max_fee_limit(1) .script_gas_limit(GAS_LIMIT) .add_input(input) .add_input(gas_coin) @@ -123,7 +128,8 @@ async fn faulty_t2_collided_on_contract_id_from_tx1() { Default::default(), Default::default(), ) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .add_input(gas_coin) .add_output(create_contract_output(contract_id)) .add_output(output) @@ -139,7 +145,8 @@ async fn faulty_t2_collided_on_contract_id_from_tx1() { Default::default(), Default::default(), ) - .gas_price(9) + .tip(9) + .max_fee_limit(9) .add_input(gas_coin) .add_input(input) .add_output(create_contract_output(contract_id)) @@ -179,7 +186,9 @@ async fn fail_to_insert_tx_with_dependency_on_invalid_utxo_type() { // create a second transaction with utxo id referring to // the wrong type of utxo (contract instead of coin) let tx = TransactionBuilder::script(vec![], vec![]) - .gas_price(1) + .tip(1) + .max_fee_limit(1) + .max_fee_limit(1) .script_gas_limit(GAS_LIMIT) .add_input(context.random_predicate( AssetId::BASE, @@ -235,7 +244,8 @@ async fn try_to_insert_tx2_missing_utxo() { let input = context.random_predicate(AssetId::BASE, TEST_COIN_AMOUNT, None); let tx = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(input) .finalize_as_transaction(); @@ -259,13 +269,15 @@ async fn higher_priced_tx_removes_lower_priced_tx() { let (_, coin_input) = context.setup_coin(); let tx1 = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(coin_input.clone()) .finalize_as_transaction(); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(20) + .tip(20) + .max_fee_limit(20) .script_gas_limit(GAS_LIMIT) .add_input(coin_input) .finalize_as_transaction(); @@ -291,9 +303,10 @@ async fn underpriced_tx1_not_included_coin_collision() { let mut context = TextContext::default(); let (_, gas_coin) = context.setup_coin(); - let (output, unset_input) = context.create_output_and_input(10); + let (output, unset_input) = context.create_output_and_input(20); let tx1 = TransactionBuilder::script(vec![], vec![]) - .gas_price(20) + .tip(20) + .max_fee_limit(20) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .add_output(output) @@ -302,13 +315,15 @@ async fn underpriced_tx1_not_included_coin_collision() { let input = unset_input.into_input(UtxoId::new(tx1.id(&Default::default()), 0)); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(20) + .tip(20) + .max_fee_limit(20) .script_gas_limit(GAS_LIMIT) .add_input(input.clone()) .finalize_as_transaction(); let tx3 = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(input) .finalize_as_transaction(); @@ -345,14 +360,16 @@ async fn overpriced_tx_contract_input_not_inserted() { Default::default(), Default::default(), ) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .add_input(gas_funds) .add_output(create_contract_output(contract_id)) .finalize_as_transaction(); let (_, gas_funds) = context.setup_coin(); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(11) + .tip(11) + .max_fee_limit(11) .script_gas_limit(GAS_LIMIT) .add_input(gas_funds) .add_input(create_contract_input( @@ -393,14 +410,16 @@ async fn dependent_contract_input_inserted() { Default::default(), Default::default(), ) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .add_input(gas_funds) .add_output(create_contract_output(contract_id)) .finalize_as_transaction(); let (_, gas_funds) = context.setup_coin(); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(gas_funds) .add_input(create_contract_input( @@ -430,7 +449,8 @@ async fn more_priced_tx3_removes_tx1_and_dependent_tx2() { let (output, unset_input) = context.create_output_and_input(10); let tx1 = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin.clone()) .add_output(output) @@ -439,13 +459,15 @@ async fn more_priced_tx3_removes_tx1_and_dependent_tx2() { let input = unset_input.into_input(UtxoId::new(tx1.id(&Default::default()), 0)); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(9) + .tip(9) + .max_fee_limit(9) .script_gas_limit(GAS_LIMIT) .add_input(input) .finalize_as_transaction(); let tx3 = TransactionBuilder::script(vec![], vec![]) - .gas_price(20) + .tip(20) + .max_fee_limit(20) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .finalize_as_transaction(); @@ -482,19 +504,22 @@ async fn more_priced_tx2_removes_tx1_and_more_priced_tx3_removes_tx2() { let (_, gas_coin) = context.setup_coin(); let tx1 = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin.clone()) .finalize_as_transaction(); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(11) + .tip(11) + .max_fee_limit(11) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin.clone()) .finalize_as_transaction(); let tx3 = TransactionBuilder::script(vec![], vec![]) - .gas_price(12) + .tip(12) + .max_fee_limit(12) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .finalize_as_transaction(); @@ -607,21 +632,24 @@ async fn sorted_out_tx1_2_4() { let (_, gas_coin) = context.setup_coin(); let tx1 = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .finalize_as_transaction(); let (_, gas_coin) = context.setup_coin(); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(9) + .tip(9) + .max_fee_limit(9) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .finalize_as_transaction(); let (_, gas_coin) = context.setup_coin(); let tx3 = TransactionBuilder::script(vec![], vec![]) - .gas_price(20) + .tip(20) + .max_fee_limit(20) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .finalize_as_transaction(); @@ -660,7 +688,8 @@ async fn find_dependent_tx1_tx2() { let (_, gas_coin) = context.setup_coin(); let (output, unset_input) = context.create_output_and_input(10_000); let tx1 = TransactionBuilder::script(vec![], vec![]) - .gas_price(11) + .tip(11) + .max_fee_limit(11) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .add_output(output) @@ -669,7 +698,8 @@ async fn find_dependent_tx1_tx2() { let input = unset_input.into_input(UtxoId::new(tx1.id(&Default::default()), 0)); let (output, unset_input) = context.create_output_and_input(7_500); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(input) .add_output(output) @@ -677,7 +707,8 @@ async fn find_dependent_tx1_tx2() { let input = unset_input.into_input(UtxoId::new(tx2.id(&Default::default()), 0)); let tx3 = TransactionBuilder::script(vec![], vec![]) - .gas_price(9) + .tip(9) + .max_fee_limit(9) .script_gas_limit(GAS_LIMIT) .add_input(input) .finalize_as_transaction(); @@ -708,7 +739,7 @@ async fn find_dependent_tx1_tx2() { let mut list: Vec<_> = seen.into_values().collect(); // sort from high to low price - list.sort_by_key(|tx| Reverse(tx.price())); + list.sort_by_key(|tx| Reverse(tx.tip())); assert_eq!(list.len(), 3, "We should have three items"); assert_eq!(list[0].id(), tx1_id, "Tx1 should be first."); assert_eq!(list[1].id(), tx2_id, "Tx2 should be second."); @@ -724,7 +755,8 @@ async fn tx_at_least_min_gas_price_is_insertable() { let (_, gas_coin) = context.setup_coin(); let tx = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(1000) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .finalize_as_transaction(); @@ -740,7 +772,8 @@ async fn tx_below_min_gas_price_is_not_insertable() { let gas_coin = context.random_predicate(AssetId::BASE, TEST_COIN_AMOUNT, None); let tx = TransactionBuilder::script(vec![], vec![]) - .gas_price(10) + .tip(10) + .max_fee_limit(10) .script_gas_limit(GAS_LIMIT) .add_input(gas_coin) .finalize_as_transaction(); @@ -755,7 +788,10 @@ async fn tx_below_min_gas_price_is_not_insertable() { .await .expect_err("expected insertion failure"); - assert!(matches!(err, Error::NotInsertedGasPriceTooLow)); + assert!(matches!( + err, + Error::ConsensusValidity(CheckError::InsufficientMaxFee { .. }) + )); } #[tokio::test] @@ -837,13 +873,15 @@ async fn tx_rejected_from_pool_when_gas_price_is_lower_than_another_tx_with_same create_message_predicate_from_message(message_amount, 0); let tx_high = TransactionBuilder::script(vec![], vec![]) - .gas_price(gas_price_high) + .tip(gas_price_high) + .max_fee_limit(gas_price_high) .script_gas_limit(GAS_LIMIT) .add_input(conflicting_message_input.clone()) .finalize_as_transaction(); let tx_low = TransactionBuilder::script(vec![], vec![]) - .gas_price(gas_price_low) + .tip(gas_price_low) + .max_fee_limit(gas_price_low) .script_gas_limit(GAS_LIMIT) .add_input(conflicting_message_input) .finalize_as_transaction(); @@ -885,7 +923,8 @@ 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![]) - .gas_price(gas_price_low) + .tip(gas_price_low) + .max_fee_limit(gas_price_low) .script_gas_limit(GAS_LIMIT) .add_input(conflicting_message_input.clone()) .finalize_as_transaction(); @@ -902,7 +941,8 @@ async fn higher_priced_tx_squeezes_out_lower_priced_tx_with_same_message_id() { // prices of both the new and existing transactions. Since the existing transaction's gas // price is lower, we accept the new transaction and squeeze out the old transaction. let tx_high = TransactionBuilder::script(vec![], vec![]) - .gas_price(gas_price_high) + .tip(gas_price_high) + .max_fee_limit(gas_price_high) .script_gas_limit(GAS_LIMIT) .add_input(conflicting_message_input) .finalize_as_transaction(); @@ -927,20 +967,23 @@ async fn message_of_squeezed_out_tx_can_be_resubmitted_at_lower_gas_price() { // Insert a tx for the message id with a low gas amount let tx1 = TransactionBuilder::script(vec![], vec![]) - .gas_price(2) + .tip(2) + .max_fee_limit(2) .script_gas_limit(GAS_LIMIT) .add_input(message_input_1.clone()) .add_input(message_input_2.clone()) .finalize_as_transaction(); let tx2 = TransactionBuilder::script(vec![], vec![]) - .gas_price(3) + .tip(3) + .max_fee_limit(3) .script_gas_limit(GAS_LIMIT) .add_input(message_input_1) .finalize_as_transaction(); let tx3 = TransactionBuilder::script(vec![], vec![]) - .gas_price(1) + .tip(1) + .max_fee_limit(1) .script_gas_limit(GAS_LIMIT) .add_input(message_input_2) .finalize_as_transaction(); diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 9a6d6ba832..fb53eadc9c 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -35,6 +35,7 @@ pub mod vm_storage; pub use fuel_vm_private::storage::{ ContractsAssetKey, + ContractsStateData, ContractsStateKey, }; #[doc(hidden)] diff --git a/crates/storage/src/structured_storage.rs b/crates/storage/src/structured_storage.rs index 615bff7ae2..903dc67e2c 100644 --- a/crates/storage/src/structured_storage.rs +++ b/crates/storage/src/structured_storage.rs @@ -7,6 +7,11 @@ use crate::{ SupportsBatching, SupportsMerkle, }, + codec::{ + raw::Raw, + Encode, + Encoder, + }, kv_store::{ BatchOperations, KeyValueStore, @@ -19,9 +24,14 @@ use crate::{ StorageBatchMutate, StorageInspect, StorageMutate, + StorageRead, StorageSize, + StorageWrite, +}; +use std::{ + borrow::Cow, + ops::Deref, }; -use std::borrow::Cow; pub mod balances; pub mod blocks; @@ -173,6 +183,83 @@ where } } +impl StorageRead for StructuredStorage +where + S: KeyValueStore, + M: Mappable + TableWithBlueprint, + M::Blueprint: Blueprint, +{ + fn read( + &self, + key: &::Key, + buf: &mut [u8], + ) -> Result, Self::Error> { + let key_encoder = >::KeyCodec::encode(key); + let key_bytes = key_encoder.as_bytes(); + self.storage + .read(key_bytes.as_ref(), ::column(), buf) + } + + fn read_alloc( + &self, + key: &::Key, + ) -> Result>, Self::Error> { + let key_encoder = >::KeyCodec::encode(key); + let key_bytes = key_encoder.as_bytes(); + self.storage + .get(key_bytes.as_ref(), ::column()) + // TODO: Return `Value` instead of cloned `Vec`. + .map(|value| value.map(|value| value.deref().clone())) + } +} + +impl StorageWrite for StructuredStorage +where + S: KeyValueStore, + M: TableWithBlueprint, + M::Blueprint: Blueprint, + // TODO: Add new methods to the `Blueprint` that allows work with bytes directly + // without deserialization into `OwnedValue`. + M::OwnedValue: Into>, +{ + fn write(&mut self, key: &M::Key, buf: &[u8]) -> Result { + ::Blueprint::put( + &mut self.storage, + key, + M::column(), + buf, + ) + .map(|_| buf.len()) + } + + fn replace( + &mut self, + key: &M::Key, + buf: &[u8], + ) -> Result<(usize, Option>), Self::Error> { + let bytes_written = buf.len(); + let prev = ::Blueprint::replace( + &mut self.storage, + key, + M::column(), + buf, + )? + .map(|prev| prev.into()); + let result = (bytes_written, prev); + Ok(result) + } + + fn take(&mut self, key: &M::Key) -> Result>, Self::Error> { + let take = ::Blueprint::take( + &mut self.storage, + key, + M::column(), + )? + .map(|value| value.into()); + Ok(take) + } +} + /// The module that provides helper macros for testing the structured storage. #[cfg(feature = "test-helpers")] pub mod test { diff --git a/crates/storage/src/structured_storage/contracts.rs b/crates/storage/src/structured_storage/contracts.rs index 8a9b4fd57b..2189caa6d9 100644 --- a/crates/storage/src/structured_storage/contracts.rs +++ b/crates/storage/src/structured_storage/contracts.rs @@ -7,20 +7,13 @@ use crate::{ raw::Raw, }, column::Column, - kv_store::KeyValueStore, - structured_storage::{ - StructuredStorage, - TableWithBlueprint, - }, + structured_storage::TableWithBlueprint, tables::{ ContractsInfo, ContractsLatestUtxo, ContractsRawCode, }, - StorageRead, }; -use core::ops::Deref; -use fuel_core_types::fuel_tx::ContractId; // # Dev-note: The value of the `ContractsRawCode` has a unique implementation of serialization // and deserialization and uses `Raw` codec. Because the value is a contract byte code represented @@ -35,26 +28,6 @@ impl TableWithBlueprint for ContractsRawCode { } } -impl StorageRead for StructuredStorage -where - S: KeyValueStore, -{ - fn read( - &self, - key: &ContractId, - buf: &mut [u8], - ) -> Result, Self::Error> { - self.storage - .read(key.as_ref(), Column::ContractsRawCode, buf) - } - - fn read_alloc(&self, key: &ContractId) -> Result>, Self::Error> { - self.storage - .get(key.as_ref(), Column::ContractsRawCode) - .map(|value| value.map(|value| value.deref().clone())) - } -} - impl TableWithBlueprint for ContractsInfo { type Blueprint = Plain; type Column = Column; diff --git a/crates/storage/src/structured_storage/state.rs b/crates/storage/src/structured_storage/state.rs index 31c5672483..2c6a8a5568 100644 --- a/crates/storage/src/structured_storage/state.rs +++ b/crates/storage/src/structured_storage/state.rs @@ -72,15 +72,15 @@ mod test { crate::basic_storage_tests!( ContractsState, ::Key::default(), - ::Value::zeroed(), - ::Value::zeroed(), + vec![0u8; 32], + vec![0u8; 32].into(), generate_key_for_same_contract ); - fn generate_value(rng: &mut impl rand::Rng) -> ::Value { + fn generate_value(rng: &mut impl rand::Rng) -> Vec { let mut bytes = [0u8; 32]; rng.fill(bytes.as_mut()); - bytes.into() + bytes.to_vec() } crate::root_storage_tests!( @@ -92,3 +92,246 @@ mod test { generate_value ); } + +#[cfg(test)] +#[allow(non_snake_case)] +mod structured_storage_tests { + use crate::{ + column::Column, + structured_storage::{ + test::InMemoryStorage, + StructuredStorage, + }, + StorageAsMut, + StorageMutate, + StorageWrite, + }; + use fuel_vm_private::{ + prelude::{ + Bytes32, + ContractId, + }, + storage::{ + ContractsState, + ContractsStateKey, + }, + }; + use rand::{ + prelude::StdRng, + Rng, + SeedableRng, + }; + + #[test] + fn storage_write__write__generates_the_same_merkle_root_as_storage_insert() { + type Storage = InMemoryStorage; + type Structure = StructuredStorage; + + let mut rng = StdRng::seed_from_u64(1234); + + // Given + let contract_id = ContractId::default(); + let keys = std::iter::from_fn(|| Some(rng.gen::())) + .take(10) + .map(|state_key| ContractsStateKey::from((&contract_id, &state_key))) + .collect::>(); + let value = vec![0u8; 32]; + + // When + let merkle_root_write = { + let storage = Storage::default(); + let mut structure = StructuredStorage::new(storage); + let mut merkle_root = structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root"); + for key in keys.iter() { + >::write( + &mut structure, + key, + &value, + ) + .expect("Unable to write storage"); + let new_merkle_root = structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root"); + assert_ne!(merkle_root, new_merkle_root); + merkle_root = new_merkle_root; + } + + structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root") + }; + + // Then + let merkle_root_insert = { + let storage = Storage::default(); + let mut structure = StructuredStorage::new(storage); + for key in keys.iter() { + >::insert( + &mut structure, + key, + &value, + ) + .expect("Unable to write storage"); + } + + structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root") + }; + + assert_eq!(merkle_root_write, merkle_root_insert); + } + + #[test] + fn storage_write__replace__generates_the_same_merkle_root_as_storage_insert() { + type Storage = InMemoryStorage; + type Structure = StructuredStorage; + + let mut rng = StdRng::seed_from_u64(1234); + + // Given + let contract_id = ContractId::default(); + let keys = std::iter::from_fn(|| Some(rng.gen::())) + .take(10) + .map(|state_key| ContractsStateKey::from((&contract_id, &state_key))) + .collect::>(); + let value = vec![0u8; 32]; + + // When + let merkle_root_replace = { + let storage = Storage::default(); + let mut structure = StructuredStorage::new(storage); + let mut merkle_root = structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root"); + for key in keys.iter() { + >::replace( + &mut structure, + key, + &value, + ) + .expect("Unable to write storage"); + let new_merkle_root = structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root"); + assert_ne!(merkle_root, new_merkle_root); + merkle_root = new_merkle_root; + } + + structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root") + }; + + // Then + let merkle_root_insert = { + let storage = Storage::default(); + let mut structure = StructuredStorage::new(storage); + for key in keys.iter() { + >::insert( + &mut structure, + key, + &value, + ) + .expect("Unable to write storage"); + } + + structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root") + }; + + assert_eq!(merkle_root_replace, merkle_root_insert); + } + + #[test] + fn storage_write__take__generates_the_same_merkle_root_as_storage_remove() { + type Storage = InMemoryStorage; + type Structure = StructuredStorage; + + let mut rng = StdRng::seed_from_u64(1234); + + // Given + let contract_id = ContractId::default(); + let keys = std::iter::from_fn(|| Some(rng.gen::())) + .take(10) + .map(|state_key| ContractsStateKey::from((&contract_id, &state_key))) + .collect::>(); + let value = vec![0u8; 32]; + + let storage = Storage::default(); + let mut structure = StructuredStorage::new(storage); + let mut merkle_root = structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root"); + for key in keys.iter() { + >::replace( + &mut structure, + key, + &value, + ) + .expect("Unable to write storage"); + + let new_merkle_root = structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root"); + assert_ne!(merkle_root, new_merkle_root); + merkle_root = new_merkle_root; + } + + // When + let state_key = rng.gen::(); + let key = ContractsStateKey::from((&contract_id, &state_key)); + + let merkle_root_replace = { + >::write( + &mut structure, + &key, + &value, + ) + .expect("Unable to write storage"); + + >::take(&mut structure, &key) + .expect("Unable to take value from storage"); + + structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root") + }; + + // Then + let merkle_root_remove = { + >::write( + &mut structure, + &key, + &value, + ) + .expect("Unable to write storage"); + + structure + .storage::() + .remove(&key) + .expect("Unable to take value from storage"); + + structure + .storage::() + .root(&contract_id) + .expect("Unable to retrieve Merkle root") + }; + + assert_eq!(merkle_root_replace, merkle_root_remove); + } +} diff --git a/crates/storage/src/vm_storage.rs b/crates/storage/src/vm_storage.rs index d8566634ea..80ccb4fbb0 100644 --- a/crates/storage/src/vm_storage.rs +++ b/crates/storage/src/vm_storage.rs @@ -4,7 +4,6 @@ use crate::{ not_found, tables::{ ContractsAssets, - ContractsInfo, ContractsRawCode, ContractsState, FuelBlocks, @@ -41,6 +40,10 @@ use fuel_core_types::{ fuel_vm::InterpreterStorage, tai64::Tai64, }; +use fuel_vm_private::{ + fuel_storage::StorageWrite, + storage::ContractsStateData, +}; use itertools::Itertools; use primitive_types::U256; use std::borrow::Cow; @@ -161,6 +164,27 @@ where } } +impl StorageWrite for VmStorage +where + D: StorageWrite, +{ + fn write(&mut self, key: &M::Key, buf: &[u8]) -> Result { + StorageWrite::::write(&mut self.database, key, buf) + } + + fn replace( + &mut self, + key: &M::Key, + buf: &[u8], + ) -> Result<(usize, Option>), Self::Error> { + StorageWrite::::replace(&mut self.database, key, buf) + } + + fn take(&mut self, key: &M::Key) -> Result>, Self::Error> { + StorageWrite::::take(&mut self.database, key) + } +} + impl MerkleRootStorage for VmStorage where D: MerkleRootStorage, @@ -171,20 +195,19 @@ where } impl ContractsAssetsStorage for VmStorage where - D: MerkleRootStorage - + StorageMutate + D: StorageMutate { } impl InterpreterStorage for VmStorage where - D: StorageMutate - + MerkleRootStorage - + StorageMutate - + StorageMutate - + StorageMutate + D: StorageWrite + + StorageSize + StorageRead - + MerkleRootStorage + + StorageMutate + + StorageWrite + + StorageSize + + StorageRead + VmStorageRequirements, { type DataError = StorageError; @@ -237,12 +260,12 @@ where ) } - fn merkle_contract_state_range( + fn contract_state_range( &self, contract_id: &ContractId, start_key: &Bytes32, range: usize, - ) -> Result>>, Self::DataError> { + ) -> Result>>, Self::DataError> { use crate::StorageAsRef; let mut key = U256::from_big_endian(start_key.as_ref()); @@ -258,13 +281,18 @@ where Ok(results) } - fn merkle_contract_state_insert_range( + fn contract_state_insert_range<'a, I>( &mut self, contract_id: &ContractId, start_key: &Bytes32, - values: &[Bytes32], - ) -> Result { + values: I, + ) -> Result + where + I: Iterator, + { + let values: Vec<_> = values.collect(); let mut current_key = U256::from_big_endian(start_key.as_ref()); + // verify key is in range current_key .checked_add(U256::from(values.len())) @@ -292,7 +320,7 @@ where Ok(found_unset as usize) } - fn merkle_contract_state_remove_range( + fn contract_state_remove_range( &mut self, contract_id: &ContractId, start_key: &Bytes32, @@ -376,6 +404,6 @@ where let slots = slots .map(|(key, value)| (ContractsStateKey::new(contract_id, &key), value)) .collect_vec(); - self.init_storage(slots.iter().map(|kv| (&kv.0, &kv.1))) + self.init_storage(slots.iter().map(|kv| (&kv.0, kv.1.as_ref()))) } } diff --git a/crates/types/src/entities/coins/coin.rs b/crates/types/src/entities/coins/coin.rs index 9ee3f22c5a..283eb7f25c 100644 --- a/crates/types/src/entities/coins/coin.rs +++ b/crates/types/src/entities/coins/coin.rs @@ -14,7 +14,6 @@ use crate::{ fuel_types::{ Address, AssetId, - BlockHeight, }, }; @@ -31,8 +30,6 @@ pub struct Coin { /// Different incompatible coins can coexist with different asset ids. /// This is the "color" of the coin. pub asset_id: AssetId, - /// This coin cannot be spent until the given height - pub maturity: BlockHeight, /// Indexes the block and transaction this coin originated from pub tx_pointer: TxPointer, } @@ -44,7 +41,6 @@ impl Coin { owner: self.owner, amount: self.amount, asset_id: self.asset_id, - maturity: self.maturity, tx_pointer: self.tx_pointer, }) } @@ -78,8 +74,6 @@ pub struct CompressedCoinV1 { /// Different incompatible coins can coexist with different asset ids. /// This is the "color" of the coin. pub asset_id: AssetId, - /// This coin cannot be spent until the given height - pub maturity: BlockHeight, /// Indexes the block and transaction this coin originated from pub tx_pointer: TxPointer, } @@ -99,7 +93,6 @@ impl CompressedCoin { owner: coin.owner, amount: coin.amount, asset_id: coin.asset_id, - maturity: coin.maturity, tx_pointer: coin.tx_pointer, }, } @@ -147,20 +140,6 @@ impl CompressedCoin { } } - /// Get the maturity of the coin - pub fn maturity(&self) -> &BlockHeight { - match self { - CompressedCoin::V1(coin) => &coin.maturity, - } - } - - /// Set the maturity of the coin - pub fn set_maturity(&mut self, maturity: BlockHeight) { - match self { - CompressedCoin::V1(coin) => coin.maturity = maturity, - } - } - /// Get the TX Pointer of the coin pub fn tx_pointer(&self) -> &TxPointer { match self { diff --git a/crates/types/src/services/block_producer.rs b/crates/types/src/services/block_producer.rs index 5029dff246..586ea00887 100644 --- a/crates/types/src/services/block_producer.rs +++ b/crates/types/src/services/block_producer.rs @@ -11,6 +11,8 @@ pub struct Components { /// It can be a predefined vector of transactions, a stream of transactions, /// or any other type that carries the transactions. pub transactions_source: Source, + /// The gas price for all transactions in the block. + pub gas_price: u64, /// The gas limit of the block. pub gas_limit: u64, } diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index c235147663..1eee089a86 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -314,6 +314,8 @@ pub enum Error { CoinbaseCannotIncreaseBalance(anyhow::Error), #[display(fmt = "Coinbase amount mismatches with expected.")] CoinbaseAmountMismatch, + #[display(fmt = "Coinbase gas price mismatches with expected.")] + CoinbaseGasPriceMismatch, #[from] TransactionValidity(TransactionValidityError), // TODO: Replace with `fuel_core_storage::Error` when execution error will live in the @@ -385,8 +387,6 @@ impl From for Error { pub enum TransactionValidityError { #[error("Coin({0:#x}) input was already spent")] CoinAlreadySpent(UtxoId), - #[error("Coin({0:#x}) has not yet reached maturity")] - CoinHasNotMatured(UtxoId), #[error("The input coin({0:#x}) doesn't match the coin from database")] CoinMismatch(UtxoId), #[error("The specified coin({0:#x}) doesn't exist")] diff --git a/crates/types/src/services/txpool.rs b/crates/types/src/services/txpool.rs index cc57d3c9f7..4e0f6b2f1e 100644 --- a/crates/types/src/services/txpool.rs +++ b/crates/types/src/services/txpool.rs @@ -8,6 +8,7 @@ use crate::{ Inputs, Outputs, ScriptGasLimit, + Tip, }, Cacheable, Chargeable, @@ -59,22 +60,6 @@ pub enum PoolTransaction { } impl PoolTransaction { - /// Returns the gas price. - pub fn price(&self) -> Word { - match self { - PoolTransaction::Script(script) => script.transaction().price(), - PoolTransaction::Create(create) => create.transaction().price(), - } - } - - /// Returns the maximum amount of gas that the transaction can consume. - pub fn max_gas(&self) -> Word { - match self { - PoolTransaction::Script(script) => script.metadata().fee.max_gas(), - PoolTransaction::Create(create) => create.metadata().fee.max_gas(), - } - } - /// Used for accounting purposes when charging byte based fees. pub fn metered_bytes_size(&self) -> usize { match self { @@ -90,6 +75,14 @@ impl PoolTransaction { PoolTransaction::Create(create) => create.id(), } } + + /// Returns the maximum amount of gas that the transaction can consume. + pub fn max_gas(&self) -> Word { + match self { + PoolTransaction::Script(script) => script.metadata().max_gas, + PoolTransaction::Create(create) => create.metadata().max_gas, + } + } } #[allow(missing_docs)] @@ -103,6 +96,13 @@ impl PoolTransaction { } } + pub fn tip(&self) -> Word { + match self { + Self::Script(script) => script.transaction().tip(), + Self::Create(create) => create.transaction().tip(), + } + } + pub fn is_computed(&self) -> bool { match self { PoolTransaction::Script(script) => script.transaction().is_computed(), diff --git a/tests/test-helpers/src/builder.rs b/tests/test-helpers/src/builder.rs index 5087db7953..3e25e6fc3f 100644 --- a/tests/test-helpers/src/builder.rs +++ b/tests/test-helpers/src/builder.rs @@ -62,7 +62,7 @@ impl TestContext { 1_000_000, script, vec![], - Policies::new().with_gas_price(0), + Policies::new().with_max_fee(0), vec![Input::coin_signed( self.rng.gen(), from, @@ -70,7 +70,6 @@ impl TestContext { Default::default(), Default::default(), Default::default(), - Default::default(), )], vec![Output::coin(to, amount, Default::default())], vec![vec![].into()], @@ -166,7 +165,6 @@ impl TestSetupBuilder { output_index: Some(utxo_id.output_index()), tx_pointer_block_height: Some(tx_pointer.block_height()), tx_pointer_tx_idx: Some(tx_pointer.tx_index()), - maturity: None, owner: *owner, amount: *amount, asset_id: *asset_id, diff --git a/tests/tests/balances.rs b/tests/tests/balances.rs index 2fae138629..6a9e8781d0 100644 --- a/tests/tests/balances.rs +++ b/tests/tests/balances.rs @@ -54,7 +54,6 @@ async fn balance() { output_index: None, tx_pointer_block_height: None, tx_pointer_tx_idx: None, - maturity: None, owner, amount, asset_id, @@ -104,7 +103,6 @@ async fn balance() { coin.asset_id, Default::default(), 0, - coin.maturity.into(), )), CoinType::MessageCoin(message) => { tx.add_input(Input::message_coin_signed( @@ -167,7 +165,6 @@ async fn first_5_balances() { output_index: None, tx_pointer_block_height: None, tx_pointer_tx_idx: None, - maturity: None, owner: *owner, amount, asset_id, diff --git a/tests/tests/blocks.rs b/tests/tests/blocks.rs index b92390cf46..cbfb33fb87 100644 --- a/tests/tests/blocks.rs +++ b/tests/tests/blocks.rs @@ -28,7 +28,6 @@ use fuel_core_types::{ consensus::Consensus, }, fuel_tx::*, - fuel_types::ChainId, secrecy::ExposeSecret, tai64::Tai64, }; @@ -96,16 +95,9 @@ async fn produce_block() { let client = FuelClient::from(srv.bound_address); let tx = Transaction::default_test_tx(); - client.submit_and_await_commit(&tx).await.unwrap(); - - let transaction_response = client - .transaction(&tx.id(&ChainId::default())) - .await - .unwrap(); + let status = client.submit_and_await_commit(&tx).await.unwrap(); - if let TransactionStatus::Success { block_height, .. } = - transaction_response.unwrap().status - { + if let TransactionStatus::Success { block_height, .. } = status { let block = client.block_by_height(block_height).await.unwrap().unwrap(); let actual_pub_key = block.block_producer().unwrap(); let block_height: u32 = block.header.height; diff --git a/tests/tests/coins.rs b/tests/tests/coins.rs index 819ee0e7a9..bc97167942 100644 --- a/tests/tests/coins.rs +++ b/tests/tests/coins.rs @@ -52,7 +52,6 @@ mod coin { output_index: None, tx_pointer_block_height: None, tx_pointer_tx_idx: None, - maturity: None, owner, amount, asset_id, @@ -120,7 +119,6 @@ mod coin { coin.amount, coin.asset_id, Default::default(), - coin.maturity.into(), ); } } @@ -512,7 +510,6 @@ mod all_coins { output_index: None, tx_pointer_block_height: None, tx_pointer_tx_idx: None, - maturity: None, owner, amount, asset_id, diff --git a/tests/tests/contract.rs b/tests/tests/contract.rs index 1698bf1592..b9ad5daf43 100644 --- a/tests/tests/contract.rs +++ b/tests/tests/contract.rs @@ -216,7 +216,6 @@ async fn can_get_message_proof() { coin.asset_id, TxPointer::default(), Default::default(), - Default::default(), predicate, vec![], ); @@ -241,7 +240,7 @@ async fn can_get_message_proof() { 1_000_000, script, script_data, - policies::Policies::new().with_gas_price(0), + policies::Policies::new().with_max_fee(0), inputs, outputs, vec![], diff --git a/tests/tests/example_tx.json b/tests/tests/example_tx.json index ba9b5a2c8a..346625301b 100755 --- a/tests/tests/example_tx.json +++ b/tests/tests/example_tx.json @@ -4,7 +4,7 @@ "script": [80,64,0,202,80,68,0,186,51,65,16,0,36,4,0,0], "script_data": [], "policies": { - "bits": "GasPrice | Maturity", + "bits": "Maturity | MaxFee", "values": [0, 0, 0, 0] }, "inputs": [ @@ -22,7 +22,25 @@ "tx_index": 0 }, "witness_index": 0, - "maturity": 0, + "predicate_gas_used": null, + "predicate": null, + "predicate_data": null + } + }, + { + "CoinSigned": { + "utxo_id": { + "tx_id": "c49d65de61cf04588a764b557d25cc6c6b4bc0d7429227e2a21e61c213b3a3e2", + "output_index": 1 + }, + "owner": "f1e92c42b90934aa6372e30bc568a326f6e66a1a0288595e6e3fbd392a4f3e6e", + "amount": 10599410012256088338, + "asset_id": "0000000000000000000000000000000000000000000000000000000000000000", + "tx_pointer": { + "block_height": 0, + "tx_index": 0 + }, + "witness_index": 0, "predicate_gas_used": null, "predicate": null, "predicate_data": null diff --git a/tests/tests/fee_collection_contract.rs b/tests/tests/fee_collection_contract.rs index c92893652a..a59611fc8e 100644 --- a/tests/tests/fee_collection_contract.rs +++ b/tests/tests/fee_collection_contract.rs @@ -48,6 +48,9 @@ struct TestContext { client: FuelClient, } +const AMOUNT: u64 = 1000; +const TIP: u64 = AMOUNT / 2; + async fn setup(rng: &mut StdRng) -> TestContext { // Make contract that coinbase fees are collected into let address: Address = rng.gen(); @@ -102,15 +105,15 @@ async fn make_block_with_fee(rng: &mut StdRng, ctx: &TestContext) { // Run a script that does nothing, but will cause fee collection let tx = TransactionBuilder::script([op::ret(RegId::ONE)].into_iter().collect(), vec![]) + .max_fee_limit(AMOUNT) + .tip(TIP) .add_unsigned_coin_input( SecretKey::random(rng), rng.gen(), - 1000, - Default::default(), + AMOUNT, Default::default(), Default::default(), ) - .gas_price(1) .script_gas_limit(1_000_000) .finalize_as_transaction(); let tx_status = ctx.client.submit_and_await_commit(&tx).await.unwrap(); @@ -158,7 +161,6 @@ async fn collect_fees(ctx: &TestContext) { .collect(), ) .add_random_fee_input() // No coinbase fee for this block - .gas_price(0) .script_gas_limit(1_000_000) .add_input(Input::contract( Default::default(), @@ -291,7 +293,6 @@ async fn missing_variable_output() { .collect(), ) .add_random_fee_input() // No coinbase fee for this block - .gas_price(0) .script_gas_limit(1_000_000) .add_input(Input::contract( Default::default(), @@ -315,7 +316,7 @@ async fn missing_variable_output() { .contract_balance(&ctx.contract_id, None) .await .unwrap(); - assert_eq!(contract_balance, 1); + assert_eq!(contract_balance, TIP); let asset_balance = ctx.client.balance(&ctx.address, None).await.unwrap(); assert_eq!(asset_balance, 0); } diff --git a/tests/tests/messages.rs b/tests/tests/messages.rs index 34eabf195f..59ac667474 100644 --- a/tests/tests/messages.rs +++ b/tests/tests/messages.rs @@ -260,7 +260,7 @@ async fn message_status__can_get_spent() { 1_000_000, vec![], vec![], - policies::Policies::new().with_gas_price(0), + policies::Policies::new().with_max_fee(0), vec![input], vec![output], vec![Vec::new().into()], @@ -413,7 +413,6 @@ async fn can_get_message_proof() { coin.asset_id, TxPointer::default(), Default::default(), - Default::default(), predicate, vec![], ); @@ -438,7 +437,7 @@ async fn can_get_message_proof() { 1_000_000, script, script_data, - policies::Policies::new().with_gas_price(0), + policies::Policies::new().with_max_fee(0), inputs, outputs, vec![], diff --git a/tests/tests/relayer.rs b/tests/tests/relayer.rs index 60f28e5797..348ae8420b 100644 --- a/tests/tests/relayer.rs +++ b/tests/tests/relayer.rs @@ -215,7 +215,6 @@ async fn messages_are_spendable_after_relayer_is_synced() { // attempt to spend the message downloaded from the relayer let tx = TransactionBuilder::script(vec![op::ret(0)].into_iter().collect(), vec![]) .script_gas_limit(10_000) - .gas_price(0) .add_unsigned_message_input(secret_key, sender, nonce, amount, vec![]) .add_output(Output::change(rng.gen(), 0, AssetId::BASE)) .finalize(); diff --git a/tests/tests/snapshot.rs b/tests/tests/snapshot.rs index 8b927f8da3..da37e03b96 100644 --- a/tests/tests/snapshot.rs +++ b/tests/tests/snapshot.rs @@ -41,8 +41,8 @@ async fn snapshot_state_config() { code: vec![8; 32], salt: Salt::new([9; 32]), state: Some(vec![ - (Bytes32::new([5u8; 32]), Bytes32::new([8u8; 32])), - (Bytes32::new([7u8; 32]), Bytes32::new([9u8; 32])), + (Bytes32::new([5u8; 32]), Bytes32::new([8u8; 32]).to_vec()), + (Bytes32::new([7u8; 32]), Bytes32::new([9u8; 32]).to_vec()), ]), balances: Some(vec![ (AssetId::new([3u8; 32]), 100), @@ -65,7 +65,6 @@ async fn snapshot_state_config() { output_index: None, tx_pointer_block_height: Some(Default::default()), tx_pointer_tx_idx: Some(0), - maturity: Some(Default::default()), owner, amount, asset_id, @@ -107,7 +106,6 @@ async fn snapshot_state_config() { state_coin[i].tx_pointer_block_height, starting_coin[i].tx_pointer_block_height ); - assert_eq!(state_coin[i].maturity, starting_coin[i].maturity); } assert_eq!(state_conf.height, starting_state.height); diff --git a/tests/tests/trigger_integration/interval.rs b/tests/tests/trigger_integration/interval.rs index 5e54d71b51..039854be65 100644 --- a/tests/tests/trigger_integration/interval.rs +++ b/tests/tests/trigger_integration/interval.rs @@ -121,7 +121,6 @@ async fn poa_interval_produces_nonempty_blocks_at_correct_rate() { rng.gen(), rng.gen(), Default::default(), - Default::default(), ) .finalize_as_transaction(); let _tx_id = client.submit(&tx).await.unwrap(); diff --git a/tests/tests/tx.rs b/tests/tests/tx.rs index 745abf36a3..f7b88d317a 100644 --- a/tests/tests/tx.rs +++ b/tests/tests/tx.rs @@ -57,7 +57,6 @@ async fn dry_run_script() { let srv = FuelService::new_node(Config::local_node()).await.unwrap(); let client = FuelClient::from(srv.bound_address); - let gas_price = Default::default(); let gas_limit = 1_000_000; let maturity = Default::default(); @@ -74,7 +73,6 @@ async fn dry_run_script() { let tx = TransactionBuilder::script(script, vec![]) .script_gas_limit(gas_limit) - .gas_price(gas_price) .maturity(maturity) .add_random_fee_input() .finalize_as_transaction(); @@ -147,7 +145,6 @@ async fn submit() { let srv = FuelService::new_node(Config::local_node()).await.unwrap(); let client = FuelClient::from(srv.bound_address); - let gas_price = Default::default(); let gas_limit = 1_000_000; let maturity = Default::default(); @@ -164,7 +161,6 @@ async fn submit() { let tx = TransactionBuilder::script(script, vec![]) .script_gas_limit(gas_limit) - .gas_price(gas_price) .maturity(maturity) .add_random_fee_input() .finalize_as_transaction(); @@ -661,7 +657,6 @@ fn create_mock_tx(val: u64) -> Transaction { 1_000_000, Default::default(), Default::default(), - Default::default(), ) .finalize_as_transaction() } diff --git a/tests/tests/tx/predicates.rs b/tests/tests/tx/predicates.rs index 526b14e416..03333ebe23 100644 --- a/tests/tests/tx/predicates.rs +++ b/tests/tests/tx/predicates.rs @@ -42,7 +42,6 @@ async fn transaction_with_valid_predicate_is_executed() { asset_id, Default::default(), Default::default(), - Default::default(), predicate, vec![], )) @@ -103,7 +102,6 @@ async fn transaction_with_invalid_predicate_is_rejected() { asset_id, Default::default(), Default::default(), - Default::default(), predicate, vec![], )) @@ -139,7 +137,6 @@ async fn transaction_with_predicates_that_exhaust_gas_limit_are_rejected() { asset_id, Default::default(), Default::default(), - Default::default(), predicate, vec![], )) diff --git a/tests/tests/tx/tx_pointer.rs b/tests/tests/tx/tx_pointer.rs index 6197259667..64c43b0382 100644 --- a/tests/tests/tx/tx_pointer.rs +++ b/tests/tests/tx/tx_pointer.rs @@ -56,7 +56,6 @@ async fn tx_pointer_set_from_genesis_for_coin_and_contract_inputs() { output_index: Some(coin_utxo_id.output_index()), tx_pointer_block_height: Some(coin_tx_pointer.block_height()), tx_pointer_tx_idx: Some(coin_tx_pointer.tx_index()), - maturity: None, owner, amount, asset_id: Default::default(), @@ -231,7 +230,6 @@ fn script_tx( ) -> Script { TransactionBuilder::script(vec![], vec![]) .script_gas_limit(10000) - .gas_price(1) .add_unsigned_coin_input( secret_key, coin_utxo_id, @@ -239,7 +237,6 @@ fn script_tx( Default::default(), // use a zeroed out txpointer Default::default(), - Default::default(), ) .add_input(Input::contract( Default::default(), diff --git a/tests/tests/tx/txn_status_subscription.rs b/tests/tests/tx/txn_status_subscription.rs index fd719cc3b7..cde9fd12e3 100644 --- a/tests/tests/tx/txn_status_subscription.rs +++ b/tests/tests/tx/txn_status_subscription.rs @@ -37,7 +37,6 @@ fn create_transaction(rng: &mut R, script: Vec) -> Transact let script = script.into_iter().collect(); let mut tx = TransactionBuilder::script(script, vec![]) .script_gas_limit(10000) - .gas_price(1) .add_input(Input::coin_predicate( rng.gen(), owner, @@ -45,7 +44,6 @@ fn create_transaction(rng: &mut R, script: Vec) -> Transact Default::default(), Default::default(), Default::default(), - Default::default(), predicate.clone(), vec![], )) @@ -56,7 +54,6 @@ fn create_transaction(rng: &mut R, script: Vec) -> Transact Default::default(), Default::default(), Default::default(), - Default::default(), predicate, vec![], )) @@ -77,7 +74,6 @@ async fn subscribe_txn_status() { let srv = FuelService::new_node(config).await.unwrap(); let client = FuelClient::from(srv.bound_address); - let gas_price = 10; let gas_limit = 1_000_000; let maturity = Default::default(); @@ -101,7 +97,6 @@ async fn subscribe_txn_status() { AssetId::zeroed(), TxPointer::default(), Default::default(), - Default::default(), predicate, vec![], ); @@ -110,8 +105,8 @@ async fn subscribe_txn_status() { script, vec![], policies::Policies::new() - .with_gas_price(gas_price + (i as u64)) - .with_maturity(maturity), + .with_maturity(maturity) + .with_max_fee(0), vec![coin_input], vec![], vec![], @@ -177,7 +172,6 @@ async fn test_regression_in_subscribe() { rng.gen(), rng.gen(), Default::default(), - Default::default(), predicate, vec![], ); diff --git a/tests/tests/tx/txpool.rs b/tests/tests/tx/txpool.rs index 030b1ef225..f14c22b52a 100644 --- a/tests/tests/tx/txpool.rs +++ b/tests/tests/tx/txpool.rs @@ -35,14 +35,12 @@ async fn txs_max_script_gas_limit() { vec![], ) .script_gas_limit(MAX_GAS_LIMIT / 2) - .gas_price(1) .add_unsigned_coin_input( SecretKey::random(&mut rng), rng.gen(), 1000 + i, Default::default(), Default::default(), - Default::default(), ) .add_output(Output::Change { amount: 0, diff --git a/tests/tests/tx/utxo_validation.rs b/tests/tests/tx/utxo_validation.rs index fedeaca149..fb634e529e 100644 --- a/tests/tests/tx/utxo_validation.rs +++ b/tests/tests/tx/utxo_validation.rs @@ -39,14 +39,12 @@ async fn submit_utxo_verified_tx_with_min_gas_price() { vec![], ) .script_gas_limit(10000) - .gas_price(1) .add_unsigned_coin_input( SecretKey::random(&mut rng), rng.gen(), 1000 + i, Default::default(), Default::default(), - Default::default(), ) .add_input(Input::contract( Default::default(), @@ -112,8 +110,8 @@ async fn submit_utxo_verified_tx_below_min_gas_price_fails() { op::ret(RegId::ONE).to_bytes().into_iter().collect(), vec![], ) + .add_random_fee_input() .script_gas_limit(100) - .gas_price(1) .finalize_as_transaction(); // initialize node with higher minimum gas price @@ -128,11 +126,8 @@ async fn submit_utxo_verified_tx_below_min_gas_price_fails() { let result = client.submit(&tx).await; assert!(result.is_err()); - assert!(result - .err() - .unwrap() - .to_string() - .contains("The gas price is too low")); + let error = result.err().unwrap().to_string(); + assert!(error.contains("InsufficientMaxFee")); } // verify that dry run can disable utxo_validation by simulating a transaction with unsigned @@ -154,7 +149,6 @@ async fn dry_run_override_utxo_validation() { AssetId::default(), Default::default(), 0, - Default::default(), )) .add_input(Input::coin_signed( rng.gen(), @@ -163,7 +157,6 @@ async fn dry_run_override_utxo_validation() { asset_id, Default::default(), 0, - Default::default(), )) .add_output(Output::change(rng.gen(), 0, asset_id)) .add_witness(Default::default()) @@ -208,7 +201,6 @@ async fn dry_run_no_utxo_validation_override() { AssetId::default(), Default::default(), 0, - Default::default(), )) .add_input(Input::coin_signed( rng.gen(), @@ -217,7 +209,6 @@ async fn dry_run_no_utxo_validation_override() { asset_id, Default::default(), 0, - Default::default(), )) .add_output(Output::change(rng.gen(), 0, asset_id)) .add_witness(Default::default()) @@ -252,7 +243,6 @@ async fn concurrent_tx_submission_produces_expected_blocks() { rng.gen_range((100000 + i as u64)..(200000 + i as u64)), Default::default(), Default::default(), - Default::default(), ) .add_output(Output::change(rng.gen(), 0, Default::default())) .finalize() diff --git a/tests/tests/tx_gossip.rs b/tests/tests/tx_gossip.rs index d24b289ec0..fdcc4575cb 100644 --- a/tests/tests/tx_gossip.rs +++ b/tests/tests/tx_gossip.rs @@ -174,7 +174,6 @@ async fn test_tx_gossiping_invalid_txs( asset_id: rng.gen(), tx_pointer: Default::default(), witness_index: 0, - maturity: Default::default(), predicate_gas_used: Empty::new(), predicate: Empty::new(), predicate_data: Empty::new(), diff --git a/tests/tests/vm_storage.rs b/tests/tests/vm_storage.rs index 79e742f443..154f09af15 100644 --- a/tests/tests/vm_storage.rs +++ b/tests/tests/vm_storage.rs @@ -6,10 +6,14 @@ mod tests { tables::ContractsState, vm_storage::VmStorage, InterpreterStorage, + StorageAsMut, StorageMutate, }; use fuel_core_txpool::types::ContractId; - use fuel_core_types::fuel_types::Bytes32; + use fuel_core_types::{ + fuel_types::Bytes32, + fuel_vm::ContractsStateKey, + }; use primitive_types::U256; use std::borrow::Cow; use test_case::test_case; @@ -33,8 +37,8 @@ mod tests { ; "read single uninitialized value" )] #[test_case( - &[(key(0), [0; 32])], key(0), 1 - => Ok(vec![Some([0; 32])]) + &[(key(0), vec![0; 32])], key(0), 1 + => Ok(vec![Some(vec![0; 32])]) ; "read single initialized value" )] #[test_case( @@ -43,33 +47,33 @@ mod tests { ; "read uninitialized range" )] #[test_case( - &[(key(1), [1; 32]), (key(2), [2; 32])], key(0), 3 - => Ok(vec![None, Some([1; 32]), Some([2; 32])]) + &[(key(1), vec![1; 32]), (key(2), vec![2; 32])], key(0), 3 + => Ok(vec![None, Some(vec![1; 32]), Some(vec![2; 32])]) ; "read uninitialized start range" )] #[test_case( - &[(key(0), [0; 32]), (key(2), [2; 32])], key(0), 3 - => Ok(vec![Some([0; 32]), None, Some([2; 32])]) + &[(key(0), vec![0; 32]), (key(2), vec![2; 32])], key(0), 3 + => Ok(vec![Some(vec![0; 32]), None, Some(vec![2; 32])]) ; "read uninitialized middle range" )] #[test_case( - &[(key(0), [0; 32]), (key(1), [1; 32])], key(0), 3 - => Ok(vec![Some([0; 32]), Some([1; 32]), None]) + &[(key(0), vec![0; 32]), (key(1), vec![1; 32])], key(0), 3 + => Ok(vec![Some(vec![0; 32]), Some(vec![1; 32]), None]) ; "read uninitialized end range" )] #[test_case( - &[(key(0), [0; 32]), (key(1), [1; 32]), (key(2), [2; 32])], key(0), 3 - => Ok(vec![Some([0; 32]), Some([1; 32]), Some([2; 32])]) + &[(key(0), vec![0; 32]), (key(1), vec![1; 32]), (key(2), vec![2; 32])], key(0), 3 + => Ok(vec![Some(vec![0; 32]), Some(vec![1; 32]), Some(vec![2; 32])]) ; "read fully initialized range" )] #[test_case( - &[(key(0), [0; 32]), (key(1), [1; 32]), (key(2), [2; 32])], key(0), 2 - => Ok(vec![Some([0; 32]), Some([1; 32])]) + &[(key(0), vec![0; 32]), (key(1), vec![1; 32]), (key(2), vec![2; 32])], key(0), 2 + => Ok(vec![Some(vec![0; 32]), Some(vec![1; 32])]) ; "read subset of initialized range" )] #[test_case( - &[(key(0), [0; 32]), (key(2), [2; 32])], key(0), 2 - => Ok(vec![Some([0; 32]), None]) + &[(key(0), vec![0; 32]), (key(2), vec![2; 32])], key(0), 2 + => Ok(vec![Some(vec![0; 32]), None]) ; "read subset of partially set range without running too far" )] #[test_case( @@ -78,15 +82,15 @@ mod tests { ; "read fails on uninitialized range if keyspace exceeded" )] #[test_case( - &[(*u256_to_bytes32(U256::MAX), [0; 32])], *u256_to_bytes32(U256::MAX), 2 + &[(*u256_to_bytes32(U256::MAX), vec![0; 32])], *u256_to_bytes32(U256::MAX), 2 => Err(()) ; "read fails on partially initialized range if keyspace exceeded" )] fn read_sequential_range( - prefilled_slots: &[([u8; 32], [u8; 32])], + prefilled_slots: &[([u8; 32], Vec)], start_key: [u8; 32], range: usize, - ) -> Result>, ()> { + ) -> Result>>, ()> { let mut db = VmStorage::::default(); let contract_id = ContractId::new([0u8; 32]); @@ -94,74 +98,73 @@ mod tests { // prefill db for (key, value) in prefilled_slots { let key = Bytes32::from(*key); - let value = Bytes32::new(*value); StorageMutate::::insert( db.database_mut(), &(&contract_id, &key).into(), - &value, + value.as_slice(), ) .unwrap(); } // perform sequential read Ok(db - .merkle_contract_state_range(&contract_id, &Bytes32::new(start_key), range) + .contract_state_range(&contract_id, &Bytes32::new(start_key), range) .map_err(|_| ())? .into_iter() - .map(|v| v.map(Cow::into_owned).map(|v| *v)) + .map(|v| v.map(Cow::into_owned).map(|v| v.0)) .collect()) } #[test_case( - &[], key(0), &[[1; 32]] + &[], key(0), &[vec![1; 32]] => Ok(false) ; "insert single value over uninitialized range" )] #[test_case( - &[(key(0), [0; 32])], key(0), &[[1; 32]] + &[(key(0), vec![0; 32])], key(0), &[vec![1; 32]] => Ok(true) ; "insert single value over initialized range" )] #[test_case( - &[], key(0), &[[1; 32], [2; 32]] + &[], key(0), &[vec![1; 32], vec![2; 32]] => Ok(false) ; "insert multiple slots over uninitialized range" )] #[test_case( - &[(key(1), [0; 32]), (key(2), [0; 32])], key(0), &[[1; 32], [2; 32], [3; 32]] + &[(key(1), vec![0; 32]), (key(2), vec![0; 32])], key(0), &[vec![1; 32], vec![2; 32], vec![3; 32]] => Ok(false) ; "insert multiple slots with uninitialized start of the range" )] #[test_case( - &[(key(0), [0; 32]), (key(2), [0; 32])], key(0), &[[1; 32], [2; 32], [3; 32]] + &[(key(0), vec![0; 32]), (key(2), vec![0; 32])], key(0), &[vec![1; 32], vec![2; 32], vec![3; 32]] => Ok(false) ; "insert multiple slots with uninitialized middle of the range" )] #[test_case( - &[(key(0), [0; 32]), (key(1), [0; 32])], key(0), &[[1; 32], [2; 32], [3; 32]] + &[(key(0), vec![0; 32]), (key(1), vec![0; 32])], key(0), &[vec![1; 32], vec![2; 32], vec![3; 32]] => Ok(false) ; "insert multiple slots with uninitialized end of the range" )] #[test_case( - &[(key(0), [0; 32]), (key(1), [0; 32]), (key(2), [0; 32])], key(0), &[[1; 32], [2; 32], [3; 32]] + &[(key(0), vec![0; 32]), (key(1), vec![0; 32]), (key(2), vec![0; 32])], key(0), &[vec![1; 32], vec![2; 32], vec![3; 32]] => Ok(true) ; "insert multiple slots over initialized range" )] #[test_case( - &[(key(0), [0; 32]), (key(1), [0; 32]), (key(2), [0; 32]), (key(3), [0; 32])], key(1), &[[1; 32], [2; 32]] + &[(key(0), vec![0; 32]), (key(1), vec![0; 32]), (key(2), vec![0; 32]), (key(3), vec![0; 32])], key(1), &[vec![1; 32], vec![2; 32]] => Ok(true) ; "insert multiple slots over sub-range of prefilled data" )] #[test_case( - &[], *u256_to_bytes32(U256::MAX), &[[1; 32], [2; 32]] + &[], *u256_to_bytes32(U256::MAX), &[vec![1; 32], vec![2; 32]] => Err(()) ; "insert fails if start_key + range > u256::MAX" )] fn insert_range( - prefilled_slots: &[([u8; 32], [u8; 32])], + prefilled_slots: &[([u8; 32], Vec)], start_key: [u8; 32], - insertion_range: &[[u8; 32]], + insertion_range: &[Vec], ) -> Result { let mut db = VmStorage::::default(); @@ -170,25 +173,21 @@ mod tests { // prefill db for (key, value) in prefilled_slots { let key = Bytes32::from(*key); - let value = Bytes32::new(*value); StorageMutate::::insert( db.database_mut(), &(&contract_id, &key).into(), - &value, + value.as_slice(), ) .unwrap(); } // test insert range let insert_status = db - .merkle_contract_state_insert_range( + .contract_state_insert_range( &contract_id, &Bytes32::new(start_key), - &insertion_range - .iter() - .map(|v| Bytes32::new(*v)) - .collect::>(), + insertion_range.iter().map(|v| v.as_slice()), ) .map_err(|_| ()) .map(|v| v == 0); @@ -199,11 +198,13 @@ mod tests { let current_key = U256::from_big_endian(&start_key).checked_add(i.into())?; let current_key = u256_to_bytes32(current_key); + let state_key = ContractsStateKey::new(&contract_id, ¤t_key); let result = db - .merkle_contract_state(&contract_id, ¤t_key) + .storage::() + .get(&state_key) .unwrap() .map(Cow::into_owned) - .map(|b| *b); + .map(|b| b.0); result }) .collect(); @@ -225,7 +226,7 @@ mod tests { ; "remove single value over uninitialized range" )] #[test_case( - &[([0; 32], [0; 32])], [0; 32], 1 + &[([0; 32], vec![0; 32])], [0; 32], 1 => (vec![], true) ; "remove single value over initialized range" )] @@ -235,30 +236,30 @@ mod tests { ; "remove multiple slots over uninitialized range" )] #[test_case( - &[([0; 32], [0; 32]), (key(1), [0; 32])], [0; 32], 2 + &[([0; 32], vec![0; 32]), (key(1), vec![0; 32])], [0; 32], 2 => (vec![], true) ; "remove multiple slots over initialized range" )] #[test_case( - &[(key(1), [0; 32]), (key(2), [0; 32])], [0; 32], 3 + &[(key(1), vec![0; 32]), (key(2), vec![0; 32])], [0; 32], 3 => (vec![], false) ; "remove multiple slots over partially uninitialized start range" )] #[test_case( - &[([0; 32], [0; 32]), (key(1), [0; 32])], [0; 32], 3 + &[([0; 32], vec![0; 32]), (key(1), vec![0; 32])], [0; 32], 3 => (vec![], false) ; "remove multiple slots over partially uninitialized end range" )] #[test_case( - &[([0; 32], [0; 32]), (key(2), [0; 32])], [0; 32], 3 + &[([0; 32], vec![0; 32]), (key(2), vec![0; 32])], [0; 32], 3 => (vec![], false) ; "remove multiple slots over partially uninitialized middle range" )] fn remove_range( - prefilled_slots: &[([u8; 32], [u8; 32])], + prefilled_slots: &[([u8; 32], Vec)], start_key: [u8; 32], remove_count: usize, - ) -> (Vec<[u8; 32]>, bool) { + ) -> (Vec>, bool) { let mut db = VmStorage::::default(); let contract_id = ContractId::new([0u8; 32]); @@ -266,19 +267,18 @@ mod tests { // prefill db for (key, value) in prefilled_slots { let key = Bytes32::from(*key); - let value = Bytes32::new(*value); StorageMutate::::insert( db.database_mut(), &(&contract_id, &key).into(), - &value, + value.as_slice(), ) .unwrap(); } // test remove range let remove_status = db - .merkle_contract_state_remove_range( + .contract_state_remove_range( &contract_id, &Bytes32::new(start_key), remove_count, @@ -297,11 +297,13 @@ mod tests { } let current_key = u256_to_bytes32(current_key); + let state_key = ContractsStateKey::new(&contract_id, ¤t_key); let result = db - .merkle_contract_state(&contract_id, ¤t_key) + .storage::() + .get(&state_key) .unwrap() .map(Cow::into_owned) - .map(|b| *b); + .map(|b| b.0); result }) .collect();