diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d94846f0ac..f6e9b884f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,9 @@ Description of the upcoming release here. - [#1408](https://github.com/FuelLabs/fuel-core/pull/1408): Update gas benchmarks for storage opcodes to use a pre-populated database to get more accurate worst-case costs. #### Breaking +- [#1407](https://github.com/FuelLabs/fuel-core/pull/1407): The recipient is a `ContractId` instead of `Address`. The block producer should deploy its contract to receive the transaction fee. The collected fee is zero until the recipient contract is set. +- [#1407](https://github.com/FuelLabs/fuel-core/pull/1407): The `Mint` transaction is reworked with new fields to support the account-base model. It affects serialization and deserialization of the transaction and also affects GraphQL schema. +- [#1407](https://github.com/FuelLabs/fuel-core/pull/1407): The `Mint` transaction is the last transaction in the block instead of the first. - [#1374](https://github.com/FuelLabs/fuel-core/pull/1374): Renamed `base_chain_height` to `da_height` and return current relayer height instead of latest Fuel block height. - [#1363](https://github.com/FuelLabs/fuel-core/pull/1363): Change message_proof api to take `nonce` instead of `message_id` - [#1339](https://github.com/FuelLabs/fuel-core/pull/1339): Added a new required field called `base_asset_id` to the `FeeParameters` definition in `ConsensusParameters`, as well as default values for `base_asset_id` in the `beta` and `dev` chainspecs. diff --git a/Cargo.lock b/Cargo.lock index 197d854b197..a9fa8acea3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2700,9 +2700,9 @@ dependencies = [ [[package]] name = "fuel-asm" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04b475eaa2b92b9b616c736974785bb25bd0babd9baa13b450bd618dc092ea6c" +checksum = "3e79e6f14f8df22e2c61956fcfc4505b38fe16b48b7a4c7cc46c5d73b90664aa" dependencies = [ "bitflags 1.3.2", "fuel-types", @@ -3207,9 +3207,9 @@ dependencies = [ [[package]] name = "fuel-crypto" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931a77cda5b2732ae538b0945e28dc6a57979f2c2b4cb601515956c48d3bb44a" +checksum = "a92ed9e3741b068beef7748973d82a8f6637fbc76427e3d822bfb19eed93a804" dependencies = [ "coins-bip32", "coins-bip39", @@ -3228,9 +3228,9 @@ dependencies = [ [[package]] name = "fuel-derive" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c8b467951f6f2b0f61eff3cd8c308b2652a7c908c60d2cf55eaf588dcd44e1c" +checksum = "92fb2450e57506fc9176adf80ac7280b66f53de683c6187351225695e609d5fe" dependencies = [ "proc-macro2", "quote", @@ -3240,29 +3240,30 @@ dependencies = [ [[package]] name = "fuel-merkle" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af06dfe8d1f1e9fcab1b61462c505073807d40b012994b66f346c19068e7272" +checksum = "f3afe7705ca2760598b1b35a6f2a114ebec8331ac4d05d78682854faecfa0cc4" dependencies = [ "derive_more", "digest 0.10.7", "fuel-storage", "hashbrown 0.13.2", "hex", + "serde", "sha2 0.10.8", ] [[package]] name = "fuel-storage" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed39a799d7a158b60cf862b8ba3cd354488f124f1b51c3929c9f8cb6c3021e2a" +checksum = "ddc3d59dadd998c035bb65040be9d6eaead1d9bba1bd106b0b9e8c28b293c9ee" [[package]] name = "fuel-tx" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5f316e3566f337869e6522c27abda5c606298c27913d93e1150b54636fc743" +checksum = "9c077189abf9e558be50ec5459ad11366b17368a62da9221facdf7efe9d88ac0" dependencies = [ "derivative", "derive_more", @@ -3282,9 +3283,9 @@ dependencies = [ [[package]] name = "fuel-types" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cb2c79b8274965438b10342a841d80eb69cca539c1439ec66dbc567c603be2" +checksum = "bea56d08a2958fb8f6969acc0130513e44796d66282b741f92aa6f33f6e4c5bc" dependencies = [ "fuel-derive", "hex", @@ -3294,9 +3295,9 @@ dependencies = [ [[package]] name = "fuel-vm" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65764050e16b8f41a0bd777f6c00593e676296cbadfff0f5d1b6f195935ff1b" +checksum = "6c3b5d3b2913553bb15c26ed644bc94b6ee06725eda6ace3001927a710c71564" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 9632b05413f..d7f9bdc939a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,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.38.0", package = "fuel-vm", default-features = false } +fuel-vm-private = { version = "0.39.0", package = "fuel-vm", default-features = false } # Common dependencies anyhow = "1.0" diff --git a/benches/benches/vm_set/blockchain.rs b/benches/benches/vm_set/blockchain.rs index 677d984e1a9..26634573c72 100644 --- a/benches/benches/vm_set/blockchain.rs +++ b/benches/benches/vm_set/blockchain.rs @@ -442,7 +442,7 @@ pub fn run(c: &mut Criterion) { let coin_output = Output::variable(Address::zeroed(), 100, AssetId::zeroed()); input.outputs.push(coin_output); let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); let coin_input = Input::coin_predicate( Default::default(), owner, @@ -525,7 +525,7 @@ pub fn run(c: &mut Criterion) { .chain(vec![2u8; i as usize]), ); let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); let coin_input = Input::coin_predicate( Default::default(), owner, diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index f6a1e3da805..1e48901c967 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -28,12 +28,9 @@ use fuel_core::{ txpool::Config as TxPoolConfig, types::{ blockchain::primitives::SecretKeyWrapper, - fuel_tx::Address, + fuel_tx::ContractId, fuel_vm::SecretKey, - secrecy::{ - ExposeSecret, - Secret, - }, + secrecy::Secret, }, }; use pyroscope::{ @@ -47,7 +44,6 @@ use pyroscope_pprofrs::{ use std::{ env, net, - ops::Deref, path::PathBuf, str::FromStr, }; @@ -285,16 +281,13 @@ impl Command { } let coinbase_recipient = if let Some(coinbase_recipient) = coinbase_recipient { - Address::from_str(coinbase_recipient.as_str()).map_err(|err| anyhow!(err))? + Some( + ContractId::from_str(coinbase_recipient.as_str()) + .map_err(|err| anyhow!(err))?, + ) } else { - consensus_key - .as_ref() - .cloned() - .map(|key| { - let sk = key.expose_secret().deref(); - Address::from(*sk.public_key().hash()) - }) - .unwrap_or_default() + tracing::warn!("The coinbase recipient `ContractId` is not set!"); + None }; let verifier = RelayerVerifierConfig { diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index dc754593caa..a309827ffbd 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -840,15 +840,19 @@ type Transaction { id: TransactionId! inputAssetIds: [AssetId!] inputContracts: [Contract!] + inputContract: InputContract gasPrice: U64 gasLimit: U64 maturity: U32 + mintAmount: U64 + mintAssetId: AssetId txPointer: TxPointer isScript: Boolean! isCreate: Boolean! isMint: Boolean! inputs: [Input!] outputs: [Output!]! + outputContract: ContractOutput witnesses: [HexString!] receiptsRoot: Bytes32 status: TransactionStatus 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 7e259efe152..857c23770ec 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 @@ -12,6 +12,15 @@ query($id: TransactionId!) { inputContracts { id } + inputContract { + utxoId + balanceRoot + stateRoot + txPointer + contract { + id + } + } inputs { __typename ... on InputCoin { @@ -79,7 +88,14 @@ query($id: TransactionId!) { stateRoot } } + outputContract { + inputIndex + balanceRoot + stateRoot + } maturity + mintAmount + mintAssetId receiptsRoot status { __typename diff --git a/crates/client/src/client/schema/tx/transparent_tx.rs b/crates/client/src/client/schema/tx/transparent_tx.rs index e2c932248c2..cda250c7385 100644 --- a/crates/client/src/client/schema/tx/transparent_tx.rs +++ b/crates/client/src/client/schema/tx/transparent_tx.rs @@ -29,6 +29,8 @@ use fuel_core_types::{ fuel_tx, fuel_tx::{ field::ReceiptsRoot, + input, + output, StorageSlot, }, fuel_types, @@ -72,30 +74,81 @@ pub struct TransactionEdge { pub node: Transaction, } +/// The `Transaction` schema is a combination of all fields available in +/// the `fuel_tx::Transaction` from each variant plus some additional +/// data from helper functions that are often fetched by the user. #[derive(cynic::QueryFragment, Debug)] #[cynic(schema_path = "./assets/schema.sdl")] pub struct Transaction { + /// The field of the `Transaction` type. pub gas_limit: Option, + /// The field of the `Transaction` type. pub gas_price: Option, + /// The field of the `Transaction` type. pub id: TransactionId, + /// The field of the `Transaction::Mint`. pub tx_pointer: Option, + /// The list of all `AssetId` from the inputs of the transaction. + /// + /// The result of a `input_asset_ids()` helper function is stored here. + /// It is not an original field of the `Transaction`. pub input_asset_ids: Option>, + /// The list of all contracts from the inputs of the transaction. + /// + /// The result of a `input_contracts()` helper function is stored here. + /// It is not an original field of the `Transaction`. pub input_contracts: Option>, + /// The field of the `Transaction::Mint` transaction. + pub input_contract: Option, + /// The field of the `Transaction` type. pub inputs: Option>, + /// It is `true` for `Transaction::Script`. + /// + /// The result of a `is_script()` helper function is stored here. + /// It is not an original field of the `Transaction`. pub is_script: bool, + /// It is `true` for `Transaction::Create`. + /// + /// The result of a `is_create()` helper function is stored here. + /// It is not an original field of the `Transaction`. pub is_create: bool, + /// It is `true` for `Transaction::Mint`. + /// + /// The result of a `is_mint()` helper function is stored here. + /// It is not an original field of the `Transaction`. pub is_mint: bool, + /// The field of the `Transaction` type. pub outputs: Vec, + /// The field of the `Transaction::Mint`. + pub output_contract: Option, + /// The field of the `Transaction::Script` and `Transaction::Create`. pub maturity: Option, + /// The field of the `Transaction::Mint`. + pub mint_amount: Option, + /// The field of the `Transaction::Mint`. + pub mint_asset_id: Option, + /// The field of the `Transaction::Script`. pub receipts_root: Option, + /// The status of the transaction fetched from the database. pub status: Option, + /// The field of the `Transaction::Script` and `Transaction::Create`. pub witnesses: Option>, + /// The receipts produced during transaction execution. It is fetched from the database.. pub receipts: Option>, + /// The field of the `Transaction::Script`. pub script: Option, + /// The field of the `Transaction::Script`. pub script_data: Option, + /// The field of the `Transaction::Create`. pub salt: Option, + /// The field of the `Transaction::Create`. pub storage_slots: Option>, + /// The field of the `Transaction::Create`. pub bytecode_witness_index: Option, + /// The size of the bytecode of the `Transaction::Create`. + /// + /// The result of a `bytecode_length()` helper function is stored here. + /// It is not an original field of the `Transaction`. pub bytecode_length: Option, } @@ -220,10 +273,26 @@ impl TryFrom for fuel_tx::Transaction { .into(); let mint = fuel_tx::Transaction::mint( tx_pointer, - tx.outputs - .into_iter() - .map(TryInto::try_into) - .collect::, ConversionError>>()?, + tx.input_contract + .ok_or_else(|| { + ConversionError::MissingField("input_contract".to_string()) + })? + .into(), + tx.output_contract + .ok_or_else(|| { + ConversionError::MissingField("output_contract".to_string()) + })? + .try_into()?, + tx.mint_amount + .ok_or_else(|| { + ConversionError::MissingField("mint_amount".to_string()) + })? + .into(), + tx.mint_asset_id + .ok_or_else(|| { + ConversionError::MissingField("mint_asset_id".to_string()) + })? + .into(), ); mint.into() }; @@ -321,13 +390,7 @@ impl TryFrom for fuel_tx::Input { ) } } - Input::InputContract(contract) => fuel_tx::Input::contract( - contract.utxo_id.into(), - contract.balance_root.into(), - contract.state_root.into(), - contract.tx_pointer.into(), - contract.contract.id.into(), - ), + Input::InputContract(contract) => fuel_tx::Input::Contract(contract.into()), Input::InputMessage(message) => { match ( message.data.0 .0.is_empty(), @@ -435,11 +498,7 @@ impl TryFrom for fuel_tx::Output { amount: coin.amount.into(), asset_id: coin.asset_id.into(), }, - Output::ContractOutput(contract) => Self::Contract { - input_index: contract.input_index.try_into()?, - balance_root: contract.balance_root.into(), - state_root: contract.state_root.into(), - }, + Output::ContractOutput(contract) => Self::Contract(contract.try_into()?), Output::ChangeOutput(change) => Self::Change { to: change.to.into(), amount: change.amount.into(), @@ -458,3 +517,27 @@ impl TryFrom for fuel_tx::Output { }) } } + +impl From for input::contract::Contract { + fn from(contract: InputContract) -> Self { + input::contract::Contract { + utxo_id: contract.utxo_id.into(), + balance_root: contract.balance_root.into(), + state_root: contract.state_root.into(), + tx_pointer: contract.tx_pointer.into(), + contract_id: contract.contract.id.into(), + } + } +} + +impl TryFrom for output::contract::Contract { + type Error = ConversionError; + + fn try_from(contract: ContractOutput) -> Result { + Ok(output::contract::Contract { + input_index: contract.input_index.try_into()?, + balance_root: contract.balance_root.into(), + state_root: contract.state_root.into(), + }) + } +} diff --git a/crates/fuel-core/src/database/vm_database.rs b/crates/fuel-core/src/database/vm_database.rs index 79dd9869a68..df4ecf3ebdb 100644 --- a/crates/fuel-core/src/database/vm_database.rs +++ b/crates/fuel-core/src/database/vm_database.rs @@ -27,7 +27,6 @@ use fuel_core_types::{ StorageSlot, }, fuel_types::{ - Address, BlockHeight, Bytes32, ContractId, @@ -45,7 +44,7 @@ use std::borrow::Cow; pub struct VmDatabase { current_block_height: BlockHeight, current_timestamp: Tai64, - coinbase: Address, + coinbase: ContractId, database: Database, } @@ -77,7 +76,7 @@ impl VmDatabase { pub fn new( database: Database, header: &ConsensusHeader, - coinbase: Address, + coinbase: ContractId, ) -> Self { Self { current_block_height: header.height, @@ -201,7 +200,7 @@ impl InterpreterStorage for VmDatabase { } } - fn coinbase(&self) -> Result { + fn coinbase(&self) -> Result { Ok(self.coinbase) } diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 2385c6142f0..0bd0f4e3dfb 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -122,6 +122,20 @@ use fuel_core_types::{ }, }; +use fuel_core_txpool::types::ContractId; +use fuel_core_types::{ + fuel_tx::{ + field::{ + InputContract, + MintAmount, + MintAssetId, + OutputContract, + }, + input, + output, + }, + fuel_vm, +}; use parking_lot::Mutex as ParkingMutex; use std::{ borrow::Cow, @@ -188,6 +202,8 @@ where struct ExecutionData { coinbase: u64, used_gas: u64, + tx_count: u16, + found_mint: bool, message_ids: Vec, tx_status: Vec, skipped_transactions: Vec<(TxId, ExecutorError)>, @@ -398,7 +414,7 @@ where component.gas_limit, ); - let execution_data = self.execute_transactions( + let execution_data = self.execute_block( &mut block_db_transaction, ExecutionType::DryRun(component), options, @@ -414,7 +430,7 @@ where component.gas_limit, ); - let execution_data = self.execute_transactions( + let execution_data = self.execute_block( &mut block_db_transaction, ExecutionType::Production(component), options, @@ -423,7 +439,7 @@ where } ExecutionTypes::Validation(mut block) => { let component = PartialBlockComponent::from_partial_block(&mut block); - let execution_data = self.execute_transactions( + let execution_data = self.execute_block( &mut block_db_transaction, ExecutionType::Validation(component), options, @@ -438,6 +454,7 @@ where message_ids, tx_status, skipped_transactions, + .. } = execution_data; // Now that the transactions have been executed, generate the full header. @@ -496,8 +513,8 @@ where } #[tracing::instrument(skip_all)] - /// Execute all transactions on the fuel block. - fn execute_transactions( + /// Execute the fuel block with all transactions. + fn execute_block( &self, block_db_transaction: &mut DatabaseTransaction, block: ExecutionType>, @@ -509,6 +526,8 @@ where let mut data = ExecutionData { coinbase: 0, used_gas: 0, + tx_count: 0, + found_mint: false, message_ids: Vec::new(), tx_status: Vec::new(), skipped_transactions: Vec::new(), @@ -528,93 +547,60 @@ where debug_assert!(block.transactions.is_empty()); let mut iter = source.next(remaining_gas_limit).into_iter().peekable(); - let mut coinbase_tx: Mint = match execution_kind { - ExecutionKind::DryRun => Default::default(), - ExecutionKind::Production => { - // The coinbase transaction should be the first. - // We will add actual amount of `Output::Coin` at the end of transactions execution. - Transaction::mint( - TxPointer::new(block_height, Default::default()), - vec![Output::coin( - self.config.coinbase_recipient, - 0, // We will set it later - *self.config.consensus_parameters.base_asset_id(), - )], - ) - } - ExecutionKind::Validation => { - let mint = if let Some(MaybeCheckedTransaction::Transaction( - Transaction::Mint(mint), - )) = iter.next() - { - mint - } else { - return Err(ExecutorError::CoinbaseIsNotFirstTransaction) - }; - self.check_coinbase(block_height, mint, None)? - } - }; - - let mut tx_index = if execution_kind != ExecutionKind::DryRun { - // Skip the coinbase transaction. - block.transactions.push(coinbase_tx.clone().into()); - 1 - } else { - 0 - }; + let mut execute_transaction = |execution_data: &mut ExecutionData, + tx: MaybeCheckedTransaction| + -> ExecutorResult<()> { + let tx_count = execution_data.tx_count; + let tx = { + let mut tx_db_transaction = block_db_transaction.transaction(); + let tx_id = tx.id(&self.config.consensus_parameters.chain_id); + let result = self.execute_transaction( + tx, + &tx_id, + &block.header, + execution_data, + execution_kind, + &mut tx_db_transaction, + options, + ); - while iter.peek().is_some() { - for transaction in iter { - let mut filter_tx = |tx: MaybeCheckedTransaction, idx| { - let mut tx_db_transaction = block_db_transaction.transaction(); - let tx_id = tx.id(&self.config.consensus_parameters.chain_id); - let result = self.execute_transaction( - idx, - tx, - &tx_id, - &block.header, - execution_data, - execution_kind, - &mut tx_db_transaction, - options, - ); - - let tx = match result { - Err(err) => { - return match execution_kind { - ExecutionKind::Production => { - // If, during block production, we get an invalid transaction, - // remove it from the block and continue block creation. An invalid - // transaction means that the caller didn't validate it first, so - // maybe something is wrong with validation rules in the `TxPool` - // (or in another place that should validate it). Or we forgot to - // clean up some dependent/conflict transactions. But it definitely - // means that something went wrong, and we must fix it. - execution_data - .skipped_transactions - .push((tx_id, err)); - None - } - ExecutionKind::DryRun | ExecutionKind::Validation => { - Some(Err(err)) - } + let tx = match result { + Err(err) => { + return match execution_kind { + ExecutionKind::Production => { + // If, during block production, we get an invalid transaction, + // remove it from the block and continue block creation. An invalid + // transaction means that the caller didn't validate it first, so + // maybe something is wrong with validation rules in the `TxPool` + // (or in another place that should validate it). Or we forgot to + // clean up some dependent/conflict transactions. But it definitely + // means that something went wrong, and we must fix it. + execution_data.skipped_transactions.push((tx_id, err)); + Ok(()) } + ExecutionKind::DryRun | ExecutionKind::Validation => Err(err), } - Ok(tx) => tx, - }; - - if let Err(err) = tx_db_transaction.commit() { - return Some(Err(err.into())) } - Some(Ok(tx)) + Ok(tx) => tx, }; - let filtered_tx = filter_tx(transaction, tx_index); - if let Some(result) = filtered_tx { - let tx = result?; - tx_index += 1; - block.transactions.push(tx); + if let Err(err) = tx_db_transaction.commit() { + return Err(err.into()) } + tx + }; + + block.transactions.push(tx); + execution_data.tx_count = tx_count + .checked_add(1) + .ok_or(ExecutorError::TooManyTransactions)?; + + Ok(()) + }; + + while iter.peek().is_some() { + for transaction in iter { + execute_transaction(&mut *execution_data, transaction)?; } remaining_gas_limit = @@ -625,36 +611,47 @@ where // After the execution of all transactions in production mode, we can set the final fee. if execution_kind == ExecutionKind::Production { - coinbase_tx.outputs_mut().clear(); - coinbase_tx.outputs_mut().push(Output::coin( - self.config.coinbase_recipient, - execution_data.coinbase, - *self.config.consensus_parameters.base_asset_id(), - )); - block.transactions[0] = coinbase_tx.clone().into(); - } + let amount_to_mint = if self.config.coinbase_recipient != ContractId::zeroed() + { + execution_data.coinbase + } else { + 0 + }; - if execution_kind != ExecutionKind::DryRun { - coinbase_tx = self.check_coinbase( - block_height, - coinbase_tx, - Some(execution_data.coinbase), - )?; - self.apply_coinbase( - coinbase_tx, - block, + let coinbase_tx = Transaction::mint( + TxPointer::new(block_height, execution_data.tx_count), + input::contract::Contract { + utxo_id: UtxoId::new(Bytes32::zeroed(), 0), + balance_root: Bytes32::zeroed(), + state_root: Bytes32::zeroed(), + tx_pointer: TxPointer::new(BlockHeight::new(0), 0), + contract_id: self.config.coinbase_recipient, + }, + output::contract::Contract { + input_index: 0, + balance_root: Bytes32::zeroed(), + state_root: Bytes32::zeroed(), + }, + amount_to_mint, + self.config.consensus_parameters.base_asset_id, + ); + + execute_transaction( execution_data, - block_db_transaction, + MaybeCheckedTransaction::Transaction(coinbase_tx.into()), )?; } + if execution_kind != ExecutionKind::DryRun && !data.found_mint { + return Err(ExecutorError::MintMissing) + } + Ok(data) } #[allow(clippy::too_many_arguments)] fn execute_transaction( &self, - idx: u16, tx: MaybeCheckedTransaction, tx_id: &TxId, header: &PartialBlockHeader, @@ -663,6 +660,10 @@ where tx_db_transaction: &mut DatabaseTransaction, options: ExecutionOptions, ) -> ExecutorResult { + if execution_data.found_mint { + return Err(ExecutorError::MintIsNotLastTransaction) + } + // Throw a clear error if the transaction id is a duplicate if tx_db_transaction .deref_mut() @@ -682,7 +683,6 @@ where match checked_tx { CheckedTransaction::Script(script) => self.execute_create_or_script( - idx, script, header, execution_data, @@ -691,7 +691,6 @@ where options, ), CheckedTransaction::Create(create) => self.execute_create_or_script( - idx, create, header, execution_data, @@ -699,93 +698,180 @@ where execution_kind, options, ), - CheckedTransaction::Mint(_) => { - // Right now, we only support `Mint` transactions for coinbase, - // which are processed separately as a first transaction. - // - // All other `Mint` transactions are not allowed. - Err(ExecutorError::NotSupportedTransaction(*tx_id)) - } + CheckedTransaction::Mint(mint) => self.execute_mint( + mint, + header, + execution_data, + tx_db_transaction, + execution_kind, + options, + ), } } - fn apply_coinbase( + fn execute_mint( &self, - coinbase_tx: Mint, - block: &PartialFuelBlock, + checked_mint: Checked, + header: &PartialBlockHeader, execution_data: &mut ExecutionData, block_db_transaction: &mut DatabaseTransaction, - ) -> ExecutorResult<()> { - let block_height = *block.header.height(); - let coinbase_id = coinbase_tx.id(&self.config.consensus_parameters.chain_id); - self.persist_output_utxos( - block_height, - 0, - &coinbase_id, - block_db_transaction, - &[], - coinbase_tx.outputs(), - )?; - execution_data.tx_status.insert( - 0, - TransactionExecutionStatus { - id: coinbase_id, - result: TransactionExecutionResult::Success { result: None }, - }, - ); - if block_db_transaction - .deref_mut() - .storage::() - .insert(&coinbase_id, &coinbase_tx.into())? - .is_some() - { - return Err(ExecutorError::TransactionIdCollision(coinbase_id)) - } - Ok(()) - } + execution_kind: ExecutionKind, + options: ExecutionOptions, + ) -> ExecutorResult { + execution_data.found_mint = true; - fn check_coinbase( - &self, - block_height: BlockHeight, - mint: Mint, - expected_amount: Option, - ) -> ExecutorResult { - let checked_mint = - mint.into_checked(block_height, &self.config.consensus_parameters)?; - - if checked_mint.transaction().tx_pointer().tx_index() != 0 { - return Err(ExecutorError::CoinbaseIsNotFirstTransaction) + if checked_mint.transaction().tx_pointer().tx_index() != execution_data.tx_count { + return Err(ExecutorError::MintHasUnexpectedIndex) } - if checked_mint.transaction().outputs().len() > 1 { - return Err(ExecutorError::CoinbaseSeveralOutputs) + let coinbase_id = checked_mint.id(); + let (mut mint, _) = checked_mint.into(); + + fn verify_mint_for_empty_contract(mint: &Mint) -> ExecutorResult<()> { + if *mint.mint_amount() != 0 { + return Err(ExecutorError::CoinbaseAmountMismatch) + } + + let input = input::contract::Contract { + utxo_id: UtxoId::new(Bytes32::zeroed(), 0), + balance_root: Bytes32::zeroed(), + state_root: Bytes32::zeroed(), + tx_pointer: TxPointer::new(BlockHeight::new(0), 0), + contract_id: ContractId::zeroed(), + }; + let output = output::contract::Contract { + input_index: 0, + balance_root: Bytes32::zeroed(), + state_root: Bytes32::zeroed(), + }; + if mint.input_contract() != &input || mint.output_contract() != &output { + return Err(ExecutorError::MintMismatch) + } + Ok(()) } - if let Some(Output::Coin { - asset_id, amount, .. - }) = checked_mint.transaction().outputs().first() - { - if asset_id != self.config.consensus_parameters.base_asset_id() { - return Err(ExecutorError::CoinbaseOutputIsInvalid) + if mint.input_contract().contract_id == ContractId::zeroed() { + verify_mint_for_empty_contract(&mint)?; + } else { + if *mint.mint_amount() != execution_data.coinbase { + return Err(ExecutorError::CoinbaseAmountMismatch) } - if let Some(expected_amount) = expected_amount { - if expected_amount != *amount { - return Err(ExecutorError::CoinbaseAmountMismatch) + let block_height = *header.height(); + + let input = mint.input_contract().clone(); + let output = *mint.output_contract(); + let mut inputs = [Input::Contract(input)]; + let mut outputs = [Output::Contract(output)]; + + if options.utxo_validation { + // validate utxos exist + self.verify_input_state( + block_db_transaction.deref(), + 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_db_transaction.deref_mut(), + options, + )?; + + let mut sub_block_db_commit = block_db_transaction.transaction(); + let sub_db_view = sub_block_db_commit.as_mut(); + let mut vm_db = VmDatabase::new( + sub_db_view.clone(), + &header.consensus, + self.config.coinbase_recipient, + ); + fuel_vm::interpreter::contract::balance_increase( + &mut vm_db, + &mint.input_contract().contract_id, + mint.mint_asset_id(), + *mint.mint_amount(), + ) + .map_err(|e| anyhow::anyhow!(e)) + .map_err(ExecutorError::CoinbaseCannotIncreaseBalance)?; + sub_block_db_commit.commit()?; + + self.persist_output_utxos( + block_height, + execution_data.tx_count, + &coinbase_id, + block_db_transaction, + 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(), + )), + }, + coinbase_id, + block_db_transaction.deref_mut(), + )?; + let Input::Contract(input) = core::mem::take(&mut inputs[0]) else { + unreachable!() + }; + let Output::Contract(output) = outputs[0] else { + unreachable!() + }; + + if execution_kind == ExecutionKind::Validation { + if mint.input_contract() != &input || mint.output_contract() != &output { + return Err(ExecutorError::MintMismatch) } + } else { + *mint.input_contract_mut() = input; + *mint.output_contract_mut() = output; } - } else { - return Err(ExecutorError::CoinbaseOutputIsInvalid) } - let (mint, _) = checked_mint.into(); - Ok(mint) + let tx = mint.into(); + + execution_data.tx_status.push(TransactionExecutionStatus { + id: coinbase_id, + result: TransactionExecutionResult::Success { result: None }, + }); + + if block_db_transaction + .deref_mut() + .storage::() + .insert(&coinbase_id, &tx)? + .is_some() + { + return Err(ExecutorError::TransactionIdCollision(coinbase_id)) + } + Ok(tx) } #[allow(clippy::too_many_arguments)] fn execute_create_or_script( &self, - idx: u16, mut checked_tx: Checked, header: &PartialBlockHeader, execution_data: &mut ExecutionData, @@ -816,7 +902,7 @@ where // validate utxos exist and maturity is properly set self.verify_input_state( tx_db_transaction.deref(), - checked_tx.transaction(), + checked_tx.transaction().inputs(), *header.height(), header.da_height, )?; @@ -857,12 +943,12 @@ where debug_assert_eq!(tx.id(&self.config.consensus_parameters.chain_id), tx_id); } - // Wrap the transaction in the execution kind. + // Wrap inputs in the execution kind. self.compute_inputs( match execution_kind { - ExecutionKind::DryRun => ExecutionTypes::DryRun(&mut tx), - ExecutionKind::Production => ExecutionTypes::Production(&mut tx), - ExecutionKind::Validation => ExecutionTypes::Validation(&tx), + ExecutionKind::DryRun => ExecutionTypes::DryRun(tx.inputs_mut()), + ExecutionKind::Production => ExecutionTypes::Production(tx.inputs_mut()), + ExecutionKind::Validation => ExecutionTypes::Validation(tx.inputs()), }, tx_id, tx_db_transaction.deref_mut(), @@ -894,26 +980,36 @@ where } // change the spent status of the tx inputs - self.spend_input_utxos(&tx, tx_db_transaction.deref_mut(), reverted)?; + self.spend_input_utxos(tx.inputs(), tx_db_transaction.deref_mut(), reverted)?; // Persist utxos first and after calculate the not utxo outputs self.persist_output_utxos( *header.height(), - idx, + execution_data.tx_count, &tx_id, tx_db_transaction.deref_mut(), 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 tx), - ExecutionKind::Production => ExecutionTypes::Production(&mut tx), - ExecutionKind::Validation => ExecutionTypes::Validation(&tx), + ExecutionKind::DryRun => { + ExecutionTypes::DryRun((&mut outputs, tx.inputs())) + } + ExecutionKind::Production => { + ExecutionTypes::Production((&mut outputs, tx.inputs())) + } + ExecutionKind::Validation => { + ExecutionTypes::Validation((&outputs, tx.inputs())) + } }, tx_id, tx_db_transaction.deref_mut(), )?; + *tx.outputs_mut() = outputs; let final_tx = tx.into(); @@ -969,21 +1065,26 @@ where Ok(final_tx) } - fn verify_input_state( + fn verify_input_state( &self, db: &Database, - transaction: &Tx, + inputs: &[Input], block_height: BlockHeight, block_da_height: DaBlockHeight, ) -> ExecutorResult<()> { - for input in transaction.inputs() { + for input in inputs { match input { Input::CoinSigned(CoinSigned { utxo_id, .. }) | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => { // TODO: Check that fields are equal. We already do that check // in the `fuel-core-txpool`, so we need to reuse the code here. if let Some(coin) = db.storage::().get(utxo_id)? { - if block_height < coin.tx_pointer.block_height() + coin.maturity { + 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, ) @@ -1093,16 +1194,13 @@ where } /// Mark input utxos as spent - fn spend_input_utxos( + fn spend_input_utxos( &self, - tx: &Tx, + inputs: &[Input], db: &mut Database, reverted: bool, - ) -> ExecutorResult<()> - where - Tx: ExecutableTransaction, - { - for input in tx.inputs() { + ) -> ExecutorResult<()> { + for input in inputs { match input { Input::CoinSigned(CoinSigned { utxo_id, .. }) | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => { @@ -1165,19 +1263,16 @@ 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, - tx: ExecutionTypes<&mut Tx, &Tx>, + inputs: ExecutionTypes<&mut [Input], &[Input]>, tx_id: TxId, db: &mut Database, options: ExecutionOptions, - ) -> ExecutorResult<()> - where - Tx: ExecutableTransaction, - { - match tx { - ExecutionTypes::DryRun(tx) | ExecutionTypes::Production(tx) => { - for input in tx.inputs_mut() { + ) -> ExecutorResult<()> { + match inputs { + ExecutionTypes::DryRun(inputs) | ExecutionTypes::Production(inputs) => { + for input in inputs { match input { Input::CoinSigned(CoinSigned { tx_pointer, @@ -1224,8 +1319,8 @@ where } } // Needed to convince the compiler that tx is taken by ref here - ExecutionTypes::Validation(tx) => { - for input in tx.inputs() { + ExecutionTypes::Validation(inputs) => { + for input in inputs { match input { Input::CoinSigned(CoinSigned { tx_pointer, @@ -1294,77 +1389,61 @@ where Ok(()) } + #[allow(clippy::type_complexity)] // TODO: Maybe we need move it to `fuel-vm`? O_o Because other `Outputs` are processed there /// 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_not_utxo_outputs( &self, - tx: ExecutionTypes<&mut Tx, &Tx>, + tx: ExecutionTypes<(&mut [Output], &[Input]), (&[Output], &[Input])>, tx_id: TxId, db: &mut Database, - ) -> ExecutorResult<()> - where - Tx: ExecutableTransaction, - { + ) -> ExecutorResult<()> { match tx { ExecutionTypes::DryRun(tx) | ExecutionTypes::Production(tx) => { - // TODO: Inputs, in most cases, are heavier than outputs, so cloning them, but we - // to avoid it in the future. - let mut outputs = tx.outputs().clone(); - for output in outputs.iter_mut() { - if let Output::Contract { - ref mut balance_root, - ref mut state_root, - ref input_index, - } = output - { - let contract_id = if let Some(Input::Contract(Contract { - contract_id, - .. - })) = tx.inputs().get(*input_index as usize) - { - contract_id - } else { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - }; + 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); - *balance_root = contract.balance_root()?; - *state_root = contract.state_root()?; + contract_output.balance_root = contract.balance_root()?; + contract_output.state_root = contract.state_root()?; } } - *tx.outputs_mut() = outputs; } ExecutionTypes::Validation(tx) => { - for output in tx.outputs() { - if let Output::Contract { - balance_root, - state_root, - input_index, - } = output - { - let contract_id = if let Some(Input::Contract(Contract { - contract_id, - .. - })) = tx.inputs().get(*input_index as usize) - { - contract_id - } else { - return Err(ExecutorError::InvalidTransactionOutcome { - transaction_id: tx_id, - }) - }; + 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 balance_root != &contract.balance_root()? { + if contract_output.balance_root != contract.balance_root()? { return Err(ExecutorError::InvalidTransactionOutcome { transaction_id: tx_id, }) } - if state_root != &contract.state_root()? { + if contract_output.state_root != contract.state_root()? { return Err(ExecutorError::InvalidTransactionOutcome { transaction_id: tx_id, }) @@ -1452,12 +1531,9 @@ where to, db, )?, - Output::Contract { - input_index: input_idx, - .. - } => { + Output::Contract(contract) => { if let Some(Input::Contract(Contract { contract_id, .. })) = - inputs.get(*input_idx as usize) + inputs.get(contract.input_index as usize) { db.storage::().insert( contract_id, @@ -1561,7 +1637,7 @@ where ) -> ExecutorResult<()> { for (tx_idx, tx) in block.transactions().iter().enumerate() { let block_height = *block.header().height(); - let mut inputs = &[][..]; + let inputs; let outputs; let tx_id = tx.id(&self.config.consensus_parameters.chain_id); match tx { @@ -1573,9 +1649,7 @@ where inputs = tx.inputs().as_slice(); outputs = tx.outputs().as_slice(); } - Transaction::Mint(tx) => { - outputs = tx.outputs().as_slice(); - } + Transaction::Mint(_) => continue, } self.persist_owners_index( block_height, @@ -1615,7 +1689,7 @@ where | Output::Variable { to, .. } => { owners.push(to); } - Output::Contract { .. } | Output::ContractCreated { .. } => {} + Output::Contract(_) | Output::ContractCreated { .. } => {} } } @@ -1905,21 +1979,22 @@ mod tests { block.header().transactions_root ); assert_eq!(block.transactions().len(), 11); - assert!(block.transactions()[0].as_mint().is_some()); - assert_eq!( - block.transactions()[0].as_mint().unwrap().outputs().len(), - 1 - ); - if let Some(Output::Coin { - asset_id, - amount, - to, - }) = block.transactions()[0].as_mint().unwrap().outputs().first() - { - assert_eq!(asset_id, &AssetId::BASE); - // Expected fee is zero, because price is zero. - assert_eq!(*amount, 0); - assert_eq!(to, &Address::zeroed()); + assert!(block.transactions()[10].as_mint().is_some()); + if let Some(mint) = block.transactions()[10].as_mint() { + assert_eq!( + mint.tx_pointer(), + &TxPointer::new(*block.header().height(), 10) + ); + assert_eq!(mint.mint_asset_id(), &AssetId::BASE); + assert_eq!(mint.mint_amount(), &0); + assert_eq!(mint.input_contract().contract_id, ContractId::zeroed()); + assert_eq!(mint.input_contract().balance_root, Bytes32::zeroed()); + assert_eq!(mint.input_contract().state_root, Bytes32::zeroed()); + assert_eq!(mint.input_contract().utxo_id, UtxoId::default()); + assert_eq!(mint.input_contract().tx_pointer, TxPointer::default()); + assert_eq!(mint.output_contract().balance_root, Bytes32::zeroed()); + assert_eq!(mint.output_contract().state_root, Bytes32::zeroed()); + assert_eq!(mint.output_contract().input_index, 0); } else { panic!("Invalid outputs of coinbase"); } @@ -1927,6 +2002,7 @@ mod tests { mod coinbase { use super::*; + use fuel_core_storage::tables::ContractsRawCode; use fuel_core_types::{ fuel_asm::GTFArgs, fuel_tx::FeeParameters, @@ -1934,11 +2010,21 @@ mod tests { #[test] fn executor_commits_transactions_with_non_zero_coinbase_generation() { + // The test verifies the correctness of the coinbase contract update. + // The test generates two blocks with a non-zero fee. + // + // The first block contains one valid and one invalid transaction. + // This part of the test verifies that the invalid transaction doesn't influence + // the final fee, and the final is the same as the `max_fee` of the valid transaction. + // + // The second block contains only a valid transaction, and it uses + // the `Mint` transaction from the first block to validate the contract + // state transition between blocks. let price = 1; let limit = 0; let gas_used_by_predicates = 0; let gas_price_factor = 1; - let script = TxBuilder::new(2322u64) + let script = TxBuilder::new(1u64) .gas_limit(limit) // Set a price for the test .gas_price(price) @@ -1948,7 +2034,7 @@ mod tests { .transaction() .clone(); - let recipient = [1u8; 32].into(); + let recipient = fuel_tx::Contract::EMPTY_CONTRACT_ID; let fee_params = FeeParameters { gas_price_factor, @@ -1963,9 +2049,15 @@ mod tests { ..Default::default() }; - let producer = Executor::test(Default::default(), config); + let database = &mut Database::default(); + database + .storage::() + .insert(&recipient, &[]) + .expect("Should insert coinbase contract"); - let expected_fee_amount = TransactionFee::checked_from_values( + let producer = Executor::test(database.clone(), config); + + let expected_fee_amount_1 = TransactionFee::checked_from_values( producer.config.consensus_parameters.fee_params(), script.metered_bytes_size() as Word, gas_used_by_predicates, @@ -1977,7 +2069,9 @@ mod tests { let invalid_duplicate_tx = script.clone().into(); let mut block = Block::default(); + block.header_mut().consensus.height = 1.into(); *block.transactions_mut() = vec![script.into(), invalid_duplicate_tx]; + block.header_mut().recalculate_metadata(); let ExecutionResult { block, @@ -1992,24 +2086,118 @@ mod tests { assert_eq!(skipped_transactions.len(), 1); assert_eq!(block.transactions().len(), 2); - assert!(block.transactions()[0].as_mint().is_some()); - assert_eq!( - block.transactions()[0].as_mint().unwrap().outputs().len(), - 1 - ); - if let Some(Output::Coin { - asset_id, - amount, - to, - }) = block.transactions()[0].as_mint().unwrap().outputs().first() - { - assert_eq!(asset_id, &AssetId::BASE); - assert!(expected_fee_amount > 0); - assert_eq!(*amount, expected_fee_amount); - assert_eq!(to, &recipient); + assert!(expected_fee_amount_1 > 0); + let first_mint; + + if let Some(mint) = block.transactions()[1].as_mint() { + assert_eq!( + mint.tx_pointer(), + &TxPointer::new(*block.header().height(), 1) + ); + assert_eq!(mint.mint_asset_id(), &AssetId::BASE); + assert_eq!(mint.mint_amount(), &expected_fee_amount_1); + assert_eq!(mint.input_contract().contract_id, recipient); + assert_eq!(mint.input_contract().balance_root, Bytes32::zeroed()); + assert_eq!(mint.input_contract().state_root, Bytes32::zeroed()); + assert_eq!(mint.input_contract().utxo_id, UtxoId::default()); + assert_eq!(mint.input_contract().tx_pointer, TxPointer::default()); + assert_ne!(mint.output_contract().balance_root, Bytes32::zeroed()); + assert_eq!(mint.output_contract().state_root, Bytes32::zeroed()); + assert_eq!(mint.output_contract().input_index, 0); + first_mint = mint.clone(); + } else { + panic!("Invalid coinbase transaction"); + } + + let (asset_id, amount) = producer + .database + .contract_balances(recipient, None, None) + .next() + .unwrap() + .unwrap(); + assert_eq!(asset_id, AssetId::zeroed()); + assert_eq!(amount, expected_fee_amount_1); + + let script = TxBuilder::new(2u64) + .gas_limit(limit) + // Set a price for the test + .gas_price(price) + .coin_input(AssetId::BASE, 10000) + .change_output(AssetId::BASE) + .build() + .transaction() + .clone(); + + let expected_fee_amount_2 = TransactionFee::checked_from_values( + producer.config.consensus_parameters.fee_params(), + script.metered_bytes_size() as Word, + gas_used_by_predicates, + limit, + price, + ) + .unwrap() + .max_fee(); + + let mut block = Block::default(); + block.header_mut().consensus.height = 2.into(); + *block.transactions_mut() = vec![script.into()]; + block.header_mut().recalculate_metadata(); + + let ExecutionResult { + block, + skipped_transactions, + .. + } = producer + .execute_and_commit( + ExecutionBlock::Production(block.into()), + Default::default(), + ) + .unwrap(); + + assert_eq!(skipped_transactions.len(), 0); + assert_eq!(block.transactions().len(), 2); + + if let Some(second_mint) = block.transactions()[1].as_mint() { + assert_eq!(second_mint.tx_pointer(), &TxPointer::new(2.into(), 1)); + assert_eq!(second_mint.mint_asset_id(), &AssetId::BASE); + assert_eq!(second_mint.mint_amount(), &expected_fee_amount_2); + assert_eq!(second_mint.input_contract().contract_id, recipient); + assert_eq!( + second_mint.input_contract().balance_root, + first_mint.output_contract().balance_root + ); + assert_eq!( + second_mint.input_contract().state_root, + first_mint.output_contract().state_root + ); + assert_eq!( + second_mint.input_contract().utxo_id, + UtxoId::new(first_mint.cached_id().expect("Id exists"), 0) + ); + assert_eq!( + second_mint.input_contract().tx_pointer, + TxPointer::new(1.into(), 1) + ); + assert_ne!( + second_mint.output_contract().balance_root, + first_mint.output_contract().balance_root + ); + assert_eq!( + second_mint.output_contract().state_root, + first_mint.output_contract().state_root + ); + assert_eq!(second_mint.output_contract().input_index, 0); } else { - panic!("Invalid outputs of coinbase"); + panic!("Invalid coinbase transaction"); } + let (asset_id, amount) = producer + .database + .contract_balances(recipient, None, None) + .next() + .unwrap() + .unwrap(); + assert_eq!(asset_id, AssetId::zeroed()); + assert_eq!(amount, expected_fee_amount_1 + expected_fee_amount_2); } #[test] @@ -2066,7 +2254,7 @@ mod tests { .build() .transaction() .clone(); - let recipient = [1u8; 32].into(); + let recipient = fuel_tx::Contract::EMPTY_CONTRACT_ID; let fee_params = FeeParameters { gas_price_factor, @@ -2080,10 +2268,13 @@ mod tests { }, ..Default::default() }; + let database = &mut Database::default(); + database + .storage::() + .insert(&recipient, &[]) + .expect("Should insert coinbase contract"); - let producer = Executor::test(Default::default(), config); - - let params = producer.config.consensus_parameters.clone(); + let producer = Executor::test(database.clone(), config); let mut block = Block::default(); *block.transactions_mut() = vec![script.into()]; @@ -2116,24 +2307,21 @@ mod tests { ) .unwrap(); assert_eq!(validated_block.transactions(), produced_txs); - let (_, owned_transactions_td_id) = validator + let (asset_id, amount) = validator .database - .owned_transactions(recipient, None, None) + .contract_balances(recipient, None, None) .next() .unwrap() .unwrap(); - // Should own `Mint` transaction - assert_eq!( - owned_transactions_td_id, - produced_txs[0].id(¶ms.chain_id) - ); + assert_eq!(asset_id, AssetId::zeroed()); + assert_ne!(amount, 0); } #[test] fn execute_cb_command() { fn compare_coinbase_addresses( - config_coinbase: Address, - expected_in_tx_coinbase: Address, + config_coinbase: ContractId, + expected_in_tx_coinbase: ContractId, ) -> bool { let script = TxBuilder::new(2322u64) .gas_limit(100000) @@ -2198,48 +2386,66 @@ mod tests { } assert!(compare_coinbase_addresses( - Address::from([1u8; 32]), - Address::from([1u8; 32]) + ContractId::from([1u8; 32]), + ContractId::from([1u8; 32]) )); assert!(!compare_coinbase_addresses( - Address::from([9u8; 32]), - Address::from([1u8; 32]) + ContractId::from([9u8; 32]), + ContractId::from([1u8; 32]) )); assert!(!compare_coinbase_addresses( - Address::from([1u8; 32]), - Address::from([9u8; 32]) + ContractId::from([1u8; 32]), + ContractId::from([9u8; 32]) )); assert!(compare_coinbase_addresses( - Address::from([9u8; 32]), - Address::from([9u8; 32]) + ContractId::from([9u8; 32]), + ContractId::from([9u8; 32]) )); } #[test] - fn invalidate_is_not_first() { - let mint = Transaction::mint(TxPointer::new(Default::default(), 1), vec![]); + fn invalidate_unexpected_index() { + let mint = Transaction::mint( + TxPointer::new(Default::default(), 1), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); let mut block = Block::default(); *block.transactions_mut() = vec![mint.into()]; block.header_mut().recalculate_metadata(); - let validator = Executor::test(Default::default(), Default::default()); + let validator = Executor::test( + Default::default(), + Config { + utxo_validation_default: false, + ..Default::default() + }, + ); let validation_err = validator .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) .expect_err("Expected error because coinbase if invalid"); assert!(matches!( validation_err, - ExecutorError::CoinbaseIsNotFirstTransaction + ExecutorError::MintHasUnexpectedIndex )); } #[test] - fn invalidate_block_height() { - let mint = - Transaction::mint(TxPointer::new(1.into(), Default::default()), vec![]); + fn invalidate_is_not_last() { + let mint = Transaction::mint( + TxPointer::new(Default::default(), 0), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); + let tx = Transaction::default_test_tx(); let mut block = Block::default(); - *block.transactions_mut() = vec![mint.into()]; + *block.transactions_mut() = vec![mint.into(), tx]; block.header_mut().recalculate_metadata(); let validator = Executor::test(Default::default(), Default::default()); @@ -2248,41 +2454,29 @@ mod tests { .expect_err("Expected error because coinbase if invalid"); assert!(matches!( validation_err, - ExecutorError::InvalidTransaction( - CheckError::TransactionMintIncorrectBlockHeight - ) + ExecutorError::MintIsNotLastTransaction )); } #[test] - fn invalidate_zero_outputs() { - let mint = Transaction::mint( - TxPointer::new(Default::default(), Default::default()), - vec![], - ); - - let mut block = Block::default(); - *block.transactions_mut() = vec![mint.into()]; - block.header_mut().recalculate_metadata(); + fn invalidate_block_missed_coinbase() { + let block = Block::default(); let validator = Executor::test(Default::default(), Default::default()); let validation_err = validator .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) - .expect_err("Expected error because coinbase if invalid"); - assert!(matches!( - validation_err, - ExecutorError::CoinbaseOutputIsInvalid - )); + .expect_err("Expected error because coinbase is missing"); + assert!(matches!(validation_err, ExecutorError::MintMissing)); } #[test] - fn invalidate_more_than_one_outputs() { + fn invalidate_block_height() { let mint = Transaction::mint( - TxPointer::new(Default::default(), Default::default()), - vec![ - Output::coin(Address::from([1u8; 32]), 0, AssetId::from([3u8; 32])), - Output::coin(Address::from([2u8; 32]), 0, AssetId::from([4u8; 32])), - ], + TxPointer::new(1.into(), Default::default()), + Default::default(), + Default::default(), + Default::default(), + Default::default(), ); let mut block = Block::default(); @@ -2295,32 +2489,37 @@ mod tests { .expect_err("Expected error because coinbase if invalid"); assert!(matches!( validation_err, - ExecutorError::CoinbaseSeveralOutputs + ExecutorError::InvalidTransaction( + CheckError::TransactionMintIncorrectBlockHeight + ) )); } #[test] - fn invalidate_not_base_asset() { + fn invalidate_invalid_base_asset() { let mint = Transaction::mint( TxPointer::new(Default::default(), Default::default()), - vec![Output::coin( - Address::from([1u8; 32]), - 0, - AssetId::from([3u8; 32]), - )], + Default::default(), + Default::default(), + Default::default(), + Default::default(), ); let mut block = Block::default(); *block.transactions_mut() = vec![mint.into()]; block.header_mut().recalculate_metadata(); - let validator = Executor::test(Default::default(), Default::default()); + let mut config = Config::default(); + config.consensus_parameters.base_asset_id = [1u8; 32].into(); + let validator = Executor::test(Default::default(), config); let validation_err = validator .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) .expect_err("Expected error because coinbase if invalid"); assert!(matches!( validation_err, - ExecutorError::CoinbaseOutputIsInvalid + ExecutorError::InvalidTransaction( + CheckError::TransactionMintNonBaseAsset + ) )); } @@ -2328,7 +2527,10 @@ mod tests { fn invalidate_mismatch_amount() { let mint = Transaction::mint( TxPointer::new(Default::default(), Default::default()), - vec![Output::coin(Address::from([1u8; 32]), 123, AssetId::BASE)], + Default::default(), + Default::default(), + 123, + Default::default(), ); let mut block = Block::default(); @@ -2344,33 +2546,6 @@ mod tests { ExecutorError::CoinbaseAmountMismatch )); } - - #[test] - fn invalidate_more_than_one_mint_is_not_allowed() { - let mut block = Block::default(); - *block.transactions_mut() = vec![ - Transaction::mint( - TxPointer::new(Default::default(), Default::default()), - vec![Output::coin(Address::from([1u8; 32]), 0, AssetId::BASE)], - ) - .into(), - Transaction::mint( - TxPointer::new(Default::default(), Default::default()), - vec![Output::coin(Address::from([2u8; 32]), 0, AssetId::BASE)], - ) - .into(), - ]; - block.header_mut().recalculate_metadata(); - - let validator = Executor::test(Default::default(), Default::default()); - let validation_err = validator - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) - .expect_err("Expected error because coinbase if invalid"); - assert!(matches!( - validation_err, - ExecutorError::NotSupportedTransaction(_) - )); - } } // Ensure tx has at least one input to cover gas @@ -2411,7 +2586,7 @@ mod tests { skipped_transactions, .. } = producer - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Production(PartialBlockComponent::from_partial_block( &mut block, @@ -2428,7 +2603,7 @@ mod tests { // Produced block is valid let mut block_db_transaction = verifier.database.transaction(); verifier - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Validation(PartialBlockComponent::from_partial_block( &mut block, @@ -2438,9 +2613,9 @@ mod tests { .unwrap(); // Invalidate the block with Insufficient tx - block.transactions.push(tx); + block.transactions.insert(block.transactions.len() - 1, tx); let mut block_db_transaction = verifier.database.transaction(); - let verify_result = verifier.execute_transactions( + let verify_result = verifier.execute_block( &mut block_db_transaction, ExecutionType::Validation(PartialBlockComponent::from_partial_block( &mut block, @@ -2472,7 +2647,7 @@ mod tests { skipped_transactions, .. } = producer - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Production(PartialBlockComponent::from_partial_block( &mut block, @@ -2489,7 +2664,7 @@ mod tests { // Produced block is valid let mut block_db_transaction = verifier.database.transaction(); verifier - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Validation(PartialBlockComponent::from_partial_block( &mut block, @@ -2499,9 +2674,11 @@ mod tests { .unwrap(); // Make the block invalid by adding of the duplicating transaction - block.transactions.push(Transaction::default_test_tx()); + block + .transactions + .insert(block.transactions.len() - 1, Transaction::default_test_tx()); let mut block_db_transaction = verifier.database.transaction(); - let verify_result = verifier.execute_transactions( + let verify_result = verifier.execute_block( &mut block_db_transaction, ExecutionType::Validation(PartialBlockComponent::from_partial_block( &mut block, @@ -2558,7 +2735,7 @@ mod tests { skipped_transactions, .. } = producer - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Production(PartialBlockComponent::from_partial_block( &mut block, @@ -2579,7 +2756,7 @@ mod tests { // Produced block is valid let mut block_db_transaction = verifier.database.transaction(); verifier - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Validation(PartialBlockComponent::from_partial_block( &mut block, @@ -2591,9 +2768,9 @@ mod tests { .unwrap(); // Invalidate block by adding transaction with not existing coin - block.transactions.push(tx); + block.transactions.insert(block.transactions.len() - 1, tx); let mut block_db_transaction = verifier.database.transaction(); - let verify_result = verifier.execute_transactions( + let verify_result = verifier.execute_block( &mut block_db_transaction, ExecutionType::Validation(PartialBlockComponent::from_partial_block( &mut block, @@ -2643,7 +2820,7 @@ mod tests { .unwrap(); // modify change amount - if let Transaction::Script(script) = &mut block.transactions_mut()[1] { + if let Transaction::Script(script) = &mut block.transactions_mut()[0] { if let Output::Change { amount, .. } = &mut script.outputs_mut()[0] { *amount = fake_output_amount } @@ -2875,22 +3052,22 @@ mod tests { 3 // coinbase, `tx2` and `tx3` ); assert_eq!( - block.transactions()[1].id(&ChainId::default()), + block.transactions()[0].id(&ChainId::default()), tx2.id(&ChainId::default()) ); assert_eq!( - block.transactions()[2].id(&ChainId::default()), + block.transactions()[1].id(&ChainId::default()), tx3.id(&ChainId::default()) ); // `tx1` should be skipped. assert_eq!(skipped_transactions.len(), 1); assert_eq!(&skipped_transactions[0].0, &tx1.id(&ChainId::default())); let tx2_index_in_the_block = - block.transactions()[2].as_script().unwrap().inputs()[0] + block.transactions()[1].as_script().unwrap().inputs()[0] .tx_pointer() .unwrap() .tx_index(); - assert_eq!(tx2_index_in_the_block, 1); + assert_eq!(tx2_index_in_the_block, 0); } #[test] @@ -2920,7 +3097,7 @@ mod tests { let coin = db .storage::() .get( - block.transactions()[1].as_script().unwrap().inputs()[0] + block.transactions()[0].as_script().unwrap().inputs()[0] .utxo_id() .unwrap(), ) @@ -2977,7 +3154,7 @@ mod tests { // Assert the balance and state roots should be the same before and after execution. let empty_state = (*sparse::empty_sum()).into(); - let executed_tx = block.transactions()[2].as_script().unwrap(); + let executed_tx = block.transactions()[1].as_script().unwrap(); assert!(matches!( tx_status[2].result, TransactionExecutionResult::Success { .. } @@ -2987,7 +3164,7 @@ mod tests { assert_eq!(executed_tx.outputs()[0].state_root(), Some(&empty_state)); assert_eq!(executed_tx.outputs()[0].balance_root(), Some(&empty_state)); - let expected_tx = block.transactions()[2].clone(); + let expected_tx = block.transactions()[1].clone(); let storage_tx = executor .database .storage::() @@ -3044,9 +3221,9 @@ mod tests { // Assert the balance and state roots should be the same before and after execution. let empty_state = (*sparse::empty_sum()).into(); - let executed_tx = block.transactions()[2].as_script().unwrap(); + let executed_tx = block.transactions()[1].as_script().unwrap(); assert!(matches!( - tx_status[2].result, + tx_status[1].result, TransactionExecutionResult::Failed { .. } )); assert_eq!( @@ -3060,7 +3237,7 @@ mod tests { assert_eq!(executed_tx.inputs()[0].state_root(), Some(&empty_state)); assert_eq!(executed_tx.inputs()[0].balance_root(), Some(&empty_state)); - let expected_tx = block.transactions()[2].clone(); + let expected_tx = block.transactions()[1].clone(); let storage_tx = executor .database .storage::() @@ -3156,7 +3333,7 @@ mod tests { .unwrap(); let empty_state = (*sparse::empty_sum()).into(); - let executed_tx = block.transactions()[2].as_script().unwrap(); + let executed_tx = block.transactions()[1].as_script().unwrap(); assert!(matches!( tx_status[2].result, TransactionExecutionResult::Success { .. } @@ -3173,7 +3350,7 @@ mod tests { executed_tx.outputs()[0].balance_root() ); - let expected_tx = block.transactions()[2].clone(); + let expected_tx = block.transactions()[1].clone(); let storage_tx = executor .database .storage::() @@ -3269,7 +3446,7 @@ mod tests { .execute_and_commit(ExecutionBlock::Production(block), Default::default()) .unwrap(); - let executed_tx = block.transactions()[2].as_script().unwrap(); + let executed_tx = block.transactions()[1].as_script().unwrap(); let state_root = executed_tx.outputs()[0].state_root(); let balance_root = executed_tx.outputs()[0].balance_root(); @@ -3299,7 +3476,7 @@ mod tests { tx_status[1].result, TransactionExecutionResult::Success { .. } )); - let tx = block.transactions()[1].as_script().unwrap(); + 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); } @@ -3441,7 +3618,7 @@ mod tests { let coin = db .storage::() .get( - block.transactions()[1].as_script().unwrap().inputs()[0] + block.transactions()[0].as_script().unwrap().inputs()[0] .utxo_id() .unwrap(), ) @@ -3581,7 +3758,7 @@ mod tests { ) .unwrap(); // Corrupt the utxo_id of the contract output - if let Transaction::Script(script) = &mut second_block.transactions_mut()[1] { + if let Transaction::Script(script) = &mut second_block.transactions_mut()[0] { if let Input::Contract(Contract { utxo_id, .. }) = &mut script.inputs_mut()[0] { // use a previously valid contract id which isn't the correct one for this block @@ -3621,7 +3798,7 @@ mod tests { .unwrap(); // ensure that all utxos with an amount are stored into the utxo set - for (idx, output) in block.transactions()[2] + for (idx, output) in block.transactions()[1] .as_script() .unwrap() .outputs() @@ -3791,7 +3968,7 @@ mod tests { skipped_transactions, .. } = exec - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Production(PartialBlockComponent::from_partial_block( &mut block, @@ -3854,7 +4031,7 @@ mod tests { skipped_transactions, .. } = exec - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Production(PartialBlockComponent::from_partial_block( &mut block, @@ -3923,7 +4100,8 @@ mod tests { .unwrap(); // Invalidate block by returning back `tx` with not existing message - block.transactions_mut().push(tx); + let index = block.transactions().len() - 1; + block.transactions_mut().insert(index, tx); let res = make_executor(&[]) // No messages in the db .execute_and_commit( ExecutionBlock::Validation(block), @@ -3979,7 +4157,8 @@ mod tests { .unwrap(); // Invalidate block by return back `tx` with not ready message. - block.transactions_mut().push(tx); + let index = block.transactions().len() - 1; + block.transactions_mut().insert(index, tx); let res = make_executor(&[&message]).execute_and_commit( ExecutionBlock::Validation(block), ExecutionOptions { @@ -4015,7 +4194,7 @@ mod tests { skipped_transactions, .. } = exec - .execute_transactions( + .execute_block( &mut block_db_transaction, ExecutionType::Production(PartialBlockComponent::from_partial_block( &mut block, @@ -4038,7 +4217,7 @@ mod tests { // Produced block is valid let exec = make_executor(&[&message]); let mut block_db_transaction = exec.database.transaction(); - exec.execute_transactions( + exec.execute_block( &mut block_db_transaction, ExecutionType::Validation(PartialBlockComponent::from_partial_block( &mut block, @@ -4050,10 +4229,10 @@ mod tests { .unwrap(); // Invalidate block by return back `tx2` transaction skipped during production. - block.transactions.push(tx2); + block.transactions.insert(block.transactions.len() - 1, tx2); let exec = make_executor(&[&message]); let mut block_db_transaction = exec.database.transaction(); - let res = exec.execute_transactions( + let res = exec.execute_block( &mut block_db_transaction, ExecutionType::Validation(PartialBlockComponent::from_partial_block( &mut block, diff --git a/crates/fuel-core/src/query/message.rs b/crates/fuel-core/src/query/message.rs index c5f3d0cfaa8..b1ce17e4bb9 100644 --- a/crates/fuel-core/src/query/message.rs +++ b/crates/fuel-core/src/query/message.rs @@ -210,7 +210,8 @@ pub fn message_proof( // Cannot look beyond the genesis block return Ok(None) } - let verifiable_commit_block_height = block_height - 1u32.into(); + let verifiable_commit_block_height = + block_height.pred().expect("We checked the height above"); let block_proof = database.block_history_proof( message_block_header.height(), &verifiable_commit_block_height, diff --git a/crates/fuel-core/src/query/message/test.rs b/crates/fuel-core/src/query/message/test.rs index 5fb27cc7ba3..e8ca628066f 100644 --- a/crates/fuel-core/src/query/message/test.rs +++ b/crates/fuel-core/src/query/message/test.rs @@ -164,7 +164,7 @@ async fn can_build_message_proof() { .once() .with( eq(message_block_height), - eq(commit_block_height - 1u32.into()), + eq(commit_block_height.pred().expect("Non-zero block height")), ) .returning({ let block_proof = block_proof.clone(); diff --git a/crates/fuel-core/src/schema/dap.rs b/crates/fuel-core/src/schema/dap.rs index d8f27ffc853..97a1bf7e5cf 100644 --- a/crates/fuel-core/src/schema/dap.rs +++ b/crates/fuel-core/src/schema/dap.rs @@ -29,7 +29,6 @@ use fuel_core_types::{ Script, Transaction, }, - fuel_types::Address, fuel_vm::{ checked_transaction::{ CheckedTransaction, @@ -166,7 +165,7 @@ impl ConcreteStorage { storage.as_ref().clone(), &block.header().consensus, // TODO: Use a real coinbase address - Address::zeroed(), + Default::default(), ); Ok(vm_database) diff --git a/crates/fuel-core/src/schema/tx/input.rs b/crates/fuel-core/src/schema/tx/input.rs index a966f0adc3b..fe037180273 100644 --- a/crates/fuel-core/src/schema/tx/input.rs +++ b/crates/fuel-core/src/schema/tx/input.rs @@ -211,19 +211,7 @@ impl From<&fuel_tx::Input> for Input { predicate: HexString(predicate.clone()), predicate_data: HexString(predicate_data.clone()), }), - fuel_tx::Input::Contract(fuel_tx::input::contract::Contract { - utxo_id, - balance_root, - state_root, - tx_pointer, - contract_id, - }) => Input::Contract(InputContract { - utxo_id: UtxoId(*utxo_id), - balance_root: Bytes32(*balance_root), - state_root: Bytes32(*state_root), - tx_pointer: TxPointer(*tx_pointer), - contract_id: ContractId(*contract_id), - }), + fuel_tx::Input::Contract(contract) => Input::Contract(contract.into()), fuel_tx::Input::MessageCoinSigned( fuel_tx::input::message::MessageCoinSigned { sender, @@ -313,3 +301,22 @@ impl From<&fuel_tx::Input> for Input { } } } + +impl From<&fuel_tx::input::contract::Contract> for InputContract { + fn from(value: &fuel_tx::input::contract::Contract) -> Self { + let fuel_tx::input::contract::Contract { + utxo_id, + balance_root, + state_root, + tx_pointer, + contract_id, + } = value; + InputContract { + utxo_id: UtxoId(*utxo_id), + balance_root: Bytes32(*balance_root), + state_root: Bytes32(*state_root), + tx_pointer: TxPointer(*tx_pointer), + contract_id: ContractId(*contract_id), + } + } +} diff --git a/crates/fuel-core/src/schema/tx/output.rs b/crates/fuel-core/src/schema/tx/output.rs index 0f8acaa355f..e4ae5dde7a6 100644 --- a/crates/fuel-core/src/schema/tx/output.rs +++ b/crates/fuel-core/src/schema/tx/output.rs @@ -14,6 +14,7 @@ use async_graphql::{ use fuel_core_types::{ fuel_asm::Word, fuel_tx, + fuel_tx::output, fuel_types, }; @@ -130,15 +131,7 @@ impl From<&fuel_tx::Output> for Output { amount: *amount, asset_id: *asset_id, }), - fuel_tx::Output::Contract { - input_index, - balance_root, - state_root, - } => Output::Contract(ContractOutput { - input_index: *input_index, - balance_root: *balance_root, - state_root: *state_root, - }), + fuel_tx::Output::Contract(contract) => Output::Contract(contract.into()), fuel_tx::Output::Change { to, amount, @@ -167,3 +160,18 @@ impl From<&fuel_tx::Output> for Output { } } } + +impl From<&output::contract::Contract> for ContractOutput { + fn from(value: &output::contract::Contract) -> Self { + let output::contract::Contract { + input_index, + balance_root, + state_root, + } = value; + ContractOutput { + input_index: *input_index, + balance_root: *balance_root, + state_root: *state_root, + } + } +} diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index 0e1278f20b5..00d279d5101 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -31,6 +31,10 @@ use crate::{ U32, U64, }, + tx::{ + input, + output, + }, }, }; use async_graphql::{ @@ -47,8 +51,12 @@ use fuel_core_types::{ field::{ BytecodeLength, BytecodeWitnessIndex, + InputContract, Inputs, Maturity, + MintAmount, + MintAssetId, + OutputContract, Outputs, ReceiptsRoot, Salt as SaltField, @@ -305,7 +313,16 @@ impl Transaction { fuel_tx::Transaction::Create(create) => { Some(create.input_contracts().map(|v| Contract(*v)).collect()) } - fuel_tx::Transaction::Mint(_) => None, + fuel_tx::Transaction::Mint(mint) => { + Some(vec![Contract(mint.input_contract().contract_id)]) + } + } + } + + async fn input_contract(&self) -> Option { + match &self.0 { + fuel_tx::Transaction::Script(_) | fuel_tx::Transaction::Create(_) => None, + fuel_tx::Transaction::Mint(mint) => Some(mint.input_contract().into()), } } @@ -333,6 +350,20 @@ impl Transaction { } } + async fn mint_amount(&self) -> Option { + match &self.0 { + fuel_tx::Transaction::Script(_) | fuel_tx::Transaction::Create(_) => None, + fuel_tx::Transaction::Mint(mint) => Some((*mint.mint_amount()).into()), + } + } + + async fn mint_asset_id(&self) -> Option { + match &self.0 { + fuel_tx::Transaction::Script(_) | fuel_tx::Transaction::Create(_) => None, + fuel_tx::Transaction::Mint(mint) => Some((*mint.mint_asset_id()).into()), + } + } + // TODO: Maybe we need to do the same `Script` and `Create` async fn tx_pointer(&self) -> Option { match &self.0 { @@ -374,9 +405,14 @@ impl Transaction { fuel_tx::Transaction::Create(create) => { create.outputs().iter().map(Into::into).collect() } - fuel_tx::Transaction::Mint(mint) => { - mint.outputs().iter().map(Into::into).collect() - } + fuel_tx::Transaction::Mint(_) => vec![], + } + } + + async fn output_contract(&self) -> Option { + match &self.0 { + fuel_tx::Transaction::Script(_) | fuel_tx::Transaction::Create(_) => None, + fuel_tx::Transaction::Mint(mint) => Some(mint.output_contract().into()), } } diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 2715cfa3336..f5fb716f51a 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -75,7 +75,10 @@ pub fn init_sub_services( relayer: relayer_adapter.clone(), config: Arc::new(fuel_core_executor::Config { consensus_parameters: config.chain_conf.consensus_parameters.clone(), - coinbase_recipient: config.block_producer.coinbase_recipient, + coinbase_recipient: config + .block_producer + .coinbase_recipient + .unwrap_or_default(), backtrace: config.vm.backtrace, utxo_validation_default: config.utxo_validation, }), diff --git a/crates/services/consensus_module/poa/src/service.rs b/crates/services/consensus_module/poa/src/service.rs index 03ecb6e3903..93e1a83e284 100644 --- a/crates/services/consensus_module/poa/src/service.rs +++ b/crates/services/consensus_module/poa/src/service.rs @@ -207,7 +207,9 @@ where } fn next_height(&self) -> BlockHeight { - self.last_height + 1u32.into() + self.last_height + .succ() + .expect("It should be impossible to produce more blocks than u32::MAX") } fn next_time(&self, request_type: RequestType) -> anyhow::Result { diff --git a/crates/services/consensus_module/poa/src/verifier.rs b/crates/services/consensus_module/poa/src/verifier.rs index bcbb96c89d7..738c04f7681 100644 --- a/crates/services/consensus_module/poa/src/verifier.rs +++ b/crates/services/consensus_module/poa/src/verifier.rs @@ -41,7 +41,7 @@ pub fn verify_block_fields( "The PoA block can't have the zero height" ); - let prev_height = height - 1u32.into(); + let prev_height = height.pred().expect("We checked the height above"); let prev_root = database.block_header_merkle_root(&prev_height)?; let header = block.header(); ensure!( diff --git a/crates/services/executor/src/config.rs b/crates/services/executor/src/config.rs index 60ba426f92b..16f5bc70eb0 100644 --- a/crates/services/executor/src/config.rs +++ b/crates/services/executor/src/config.rs @@ -1,14 +1,14 @@ use fuel_core_types::fuel_tx::{ - Address, ConsensusParameters, + ContractId, }; #[derive(Clone, Debug, Default)] pub struct Config { /// Network-wide common parameters used for validating the chain pub consensus_parameters: ConsensusParameters, - /// The address of the fee recipient - pub coinbase_recipient: Address, + /// The `ContractId` of the fee recipient. + pub coinbase_recipient: ContractId, /// Print execution backtraces if transaction execution reverts. pub backtrace: bool, /// Default mode for utxo_validation diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index bd3d1c82629..ae41b800e59 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -132,9 +132,13 @@ where utxo_validation: Option, ) -> anyhow::Result> { let height = match height { - None => self.db.current_block_height()?, + None => self + .db + .current_block_height()? + .succ() + .expect("It is impossible to overflow the current block height"), Some(height) => height, - } + 1.into(); + }; let is_script = transaction.is_script(); // The dry run execution should use the state of the blockchain based on the @@ -240,7 +244,7 @@ where Err(Error::GenesisBlock.into()) } else { // get info from previous block height - let prev_height = height - 1u32.into(); + let prev_height = height.pred().expect("We checked the height above"); let previous_block = self.db.get_block(&prev_height)?; let prev_root = self.db.block_header_merkle_root(&prev_height)?; diff --git a/crates/services/producer/src/block_producer/tests.rs b/crates/services/producer/src/block_producer/tests.rs index 2656b86c873..f9e959d16c8 100644 --- a/crates/services/producer/src/block_producer/tests.rs +++ b/crates/services/producer/src/block_producer/tests.rs @@ -93,7 +93,13 @@ async fn can_produce_next_block() { let ctx = TestContext::default_from_db(db); let producer = ctx.producer(); let result = producer - .produce_and_execute_block(prev_height + 1u32.into(), Tai64::now(), 1_000_000_000) + .produce_and_execute_block( + prev_height + .succ() + .expect("The block height should be valid"), + Tai64::now(), + 1_000_000_000, + ) .await; assert!(result.is_ok()); @@ -150,7 +156,13 @@ async fn cant_produce_if_previous_block_da_height_too_high() { let producer = ctx.producer(); let err = producer - .produce_and_execute_block(prev_height + 1u32.into(), Tai64::now(), 1_000_000_000) + .produce_and_execute_block( + prev_height + .succ() + .expect("The block height should be valid"), + Tai64::now(), + 1_000_000_000, + ) .await .expect_err("expected failure"); diff --git a/crates/services/producer/src/config.rs b/crates/services/producer/src/config.rs index e8ff27eadd1..71efd451a14 100644 --- a/crates/services/producer/src/config.rs +++ b/crates/services/producer/src/config.rs @@ -1,8 +1,8 @@ -use fuel_core_types::fuel_types::Address; +use fuel_core_types::fuel_types::ContractId; #[derive(Clone, Debug, Default)] pub struct Config { pub utxo_validation: bool, - pub coinbase_recipient: Address, + pub coinbase_recipient: Option, pub metrics: bool, } diff --git a/crates/services/txpool/src/containers/dependency.rs b/crates/services/txpool/src/containers/dependency.rs index 31790c5e530..698b06bcaa6 100644 --- a/crates/services/txpool/src/containers/dependency.rs +++ b/crates/services/txpool/src/containers/dependency.rs @@ -235,7 +235,7 @@ impl Dependency { return Err(Error::NotInsertedIoWrongAssetId.into()) } } - Output::Contract { .. } => { + Output::Contract(_) => { return Err(Error::NotInsertedIoContractOutput.into()) } Output::Change { @@ -630,7 +630,7 @@ impl Dependency { }, ); } - Output::Contract { .. } => { + Output::Contract(_) => { // do nothing, this contract is already already found in dependencies. // as it is tied with input and used_by is already inserted. } @@ -651,7 +651,7 @@ impl Dependency { // recursively remove all transactions that depend on the outputs of the current tx for (index, output) in tx.outputs().iter().enumerate() { match output { - Output::Contract { .. } => { + Output::Contract(_) => { // no other transactions can depend on these types of outputs } Output::Coin { .. } | Output::Change { .. } | Output::Variable { .. } => { @@ -847,11 +847,7 @@ mod tests { "test5" ); - let output = Output::Contract { - input_index: 0, - balance_root: Default::default(), - state_root: Default::default(), - }; + let output = Output::contract(0, Default::default(), Default::default()); let out = Dependency::check_if_coin_input_can_spend_output(&output, &input, false); diff --git a/crates/services/txpool/src/test_helpers.rs b/crates/services/txpool/src/test_helpers.rs index 02419cdeb1b..3c487ccb5c5 100644 --- a/crates/services/txpool/src/test_helpers.rs +++ b/crates/services/txpool/src/test_helpers.rs @@ -30,7 +30,6 @@ use fuel_core_types::{ }, fuel_types::{ AssetId, - ChainId, Word, }, fuel_vm::checked_transaction::EstimatePredicates, @@ -101,7 +100,7 @@ pub(crate) fn random_predicate( let mut predicate_code: Vec = vec![op::ret(1)].into_iter().collect(); // append some randomizing bytes after the predicate has already returned. predicate_code.push(rng.gen()); - let owner = Input::predicate_owner(&predicate_code, &ChainId::default()); + let owner = Input::predicate_owner(&predicate_code); Input::coin_predicate( utxo_id.unwrap_or_else(|| rng.gen()), owner, @@ -123,7 +122,7 @@ pub(crate) fn custom_predicate( code: Vec, utxo_id: Option, ) -> Input { - let owner = Input::predicate_owner(&code, &ChainId::default()); + let owner = Input::predicate_owner(&code); Input::coin_predicate( utxo_id.unwrap_or_else(|| rng.gen()), owner, diff --git a/crates/services/txpool/src/transaction_selector.rs b/crates/services/txpool/src/transaction_selector.rs index 508b6418e5e..aa787a406ca 100644 --- a/crates/services/txpool/src/transaction_selector.rs +++ b/crates/services/txpool/src/transaction_selector.rs @@ -16,6 +16,9 @@ pub fn select_transactions( // Future improvements to this algorithm may take into account the parallel nature of // transactions to maximize throughput. let mut used_block_space: Word = 0; + // The type of the index for the transaction is `u16`, so we need to + // limit it to `MAX` value minus 1(because of the `Mint` transaction). + let takes_txs = u16::MAX - 1; // Pick as many transactions as we can fit into the block (greedy) includable_txs @@ -32,6 +35,7 @@ pub fn select_transactions( false } }) + .take(takes_txs as usize) .collect() } diff --git a/crates/services/txpool/src/txpool/test_helpers.rs b/crates/services/txpool/src/txpool/test_helpers.rs index 19414f09a6d..24a4a85a0e0 100644 --- a/crates/services/txpool/src/txpool/test_helpers.rs +++ b/crates/services/txpool/src/txpool/test_helpers.rs @@ -20,7 +20,7 @@ pub(crate) fn create_message_predicate_from_message( let predicate = vec![op::ret(1)].into_iter().collect::>(); let message = Message { sender: Default::default(), - recipient: Input::predicate_owner(&predicate, &Default::default()), + recipient: Input::predicate_owner(&predicate), nonce: nonce.into(), amount, data: vec![], @@ -31,7 +31,7 @@ pub(crate) fn create_message_predicate_from_message( message.clone(), Input::message_coin_predicate( message.sender, - Input::predicate_owner(&predicate, &Default::default()), + Input::predicate_owner(&predicate), message.amount, message.nonce, Default::default(), diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index c3e436c395e..2b826226ffb 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -247,18 +247,24 @@ impl ExecutionKind { pub enum Error { #[display(fmt = "Transaction id was already used: {_0:#x}")] TransactionIdCollision(Bytes32), + #[display(fmt = "Too many transactions in the block")] + TooManyTransactions, #[display(fmt = "output already exists")] OutputAlreadyExists, #[display(fmt = "The computed fee caused an integer overflow")] FeeOverflow, - #[display(fmt = "Not supported transaction: {_0:?}")] - NotSupportedTransaction(TxId), - #[display(fmt = "The first transaction in the block is not `Mint` - coinbase.")] - CoinbaseIsNotFirstTransaction, - #[display(fmt = "Coinbase should have one output.")] - CoinbaseSeveralOutputs, - #[display(fmt = "Coinbase outputs is invalid.")] - CoinbaseOutputIsInvalid, + #[display(fmt = "The block is missing `Mint` transaction.")] + MintMissing, + #[display(fmt = "Found the second entry of the `Mint` transaction in the block.")] + MintFoundSecondEntry, + #[display(fmt = "The `Mint` transaction has an unexpected index.")] + MintHasUnexpectedIndex, + #[display(fmt = "The last transaction in the block is not `Mint`.")] + MintIsNotLastTransaction, + #[display(fmt = "The `Mint` transaction mismatches expectations.")] + MintMismatch, + #[display(fmt = "Can't increase the balance of the coinbase contract: {_0}.")] + CoinbaseCannotIncreaseBalance(anyhow::Error), #[display(fmt = "Coinbase amount mismatches with expected.")] CoinbaseAmountMismatch, #[from] diff --git a/tests/tests/contract.rs b/tests/tests/contract.rs index 16b0994b8de..d8d4236b583 100644 --- a/tests/tests/contract.rs +++ b/tests/tests/contract.rs @@ -17,10 +17,7 @@ use fuel_core_client::client::{ use fuel_core_types::{ fuel_asm::*, fuel_tx::*, - fuel_types::{ - canonical::Serialize, - ChainId, - }, + fuel_types::canonical::Serialize, fuel_vm::*, }; use rstest::rstest; @@ -214,7 +211,7 @@ async fn can_get_message_proof() { .collect(); let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); let coin_input = Input::coin_predicate( Default::default(), owner, @@ -240,11 +237,7 @@ async fn can_get_message_proof() { ]; // The transaction will output a contract output and message output. - let outputs = vec![Output::Contract { - input_index: 0, - balance_root: Bytes32::zeroed(), - state_root: Bytes32::zeroed(), - }]; + let outputs = vec![Output::contract(0, Bytes32::zeroed(), Bytes32::zeroed())]; // Create the contract calling script. let script = Transaction::script( diff --git a/tests/tests/lib.rs b/tests/tests/lib.rs index 821395859c3..9932f39ab2b 100644 --- a/tests/tests/lib.rs +++ b/tests/tests/lib.rs @@ -1,4 +1,5 @@ #![deny(unused_must_use)] +#![deny(warnings)] mod balances; mod blocks; diff --git a/tests/tests/messages.rs b/tests/tests/messages.rs index 9ea87b889cc..0801c5ed3c4 100644 --- a/tests/tests/messages.rs +++ b/tests/tests/messages.rs @@ -408,7 +408,7 @@ async fn can_get_message_proof() { .collect(); let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); let coin_input = Input::coin_predicate( Default::default(), owner, @@ -434,11 +434,7 @@ async fn can_get_message_proof() { ]; // The transaction will output a contract output and message output. - let outputs = vec![Output::Contract { - input_index: 0, - balance_root: Bytes32::zeroed(), - state_root: Bytes32::zeroed(), - }]; + let outputs = vec![Output::contract(0, Bytes32::zeroed(), Bytes32::zeroed())]; // Create the contract calling script. let script = Transaction::script( diff --git a/tests/tests/tx.rs b/tests/tests/tx.rs index 3beaab9c134..61cba76ffbf 100644 --- a/tests/tests/tx.rs +++ b/tests/tests/tx.rs @@ -287,11 +287,11 @@ async fn get_transactions() { // there are 12 transactions // [ - // coinbase_tx1, tx1, coinbase_tx2, tx2, coinbase_tx3, tx3, - // coinbase_tx4, tx4, coinbase_tx5, tx5, coinbase_tx6, tx6 + // tx1, coinbase_tx1, tx2, coinbase_tx2, tx3, coinbase_tx3, + // tx4, coinbase_tx4, tx5, coinbase_tx5, tx6, coinbase_tx6, // ] - // Query for first 6: [coinbase_tx1, tx1, coinbase_tx2, tx2, coinbase_tx3, tx3] + // Query for first 6: [tx1, coinbase_tx1, tx2, coinbase_tx2, tx3, coinbase_tx3] let client = context.client; let page_request = PaginationRequest { cursor: None, @@ -305,31 +305,31 @@ async fn get_transactions() { .iter() .map(|tx| tx.transaction.id(&ChainId::default())) .collect_vec(); + assert_eq!(transactions[0], tx1); // coinbase_tx1 - assert_eq!(transactions[1], tx1); + assert_eq!(transactions[2], tx2); // coinbase_tx2 - assert_eq!(transactions[3], tx2); + assert_eq!(transactions[4], tx3); // coinbase_tx3 - assert_eq!(transactions[5], tx3); // Check pagination state for first page assert!(response.has_next_page); assert!(!response.has_previous_page); - // Query for second page 2 with last given cursor: [coinbase_tx4, tx4, coinbase_tx5, tx5] + // Query for second page 2 with last given cursor: [tx4, coinbase_tx4, tx5, coinbase_tx5] let page_request_middle_page = PaginationRequest { cursor: response.cursor.clone(), results: 4, direction: PageDirection::Forward, }; - // Query backwards from last given cursor [3]: [coinbase_tx3, tx2, coinbase_tx2, tx1, coinbase_tx1] + // Query backwards from last given cursor [3]: [tx3, coinbase_tx2, tx2, coinbase_tx1, tx1] let page_request_backwards = PaginationRequest { cursor: response.cursor.clone(), results: 6, direction: PageDirection::Backward, }; - // Query forwards from last given cursor [3]: [coinbase_tx4, tx4, coinbase_tx5, tx5, coinbase_tx6, tx6] + // Query forwards from last given cursor [3]: [tx4, coinbase_tx4, tx5, coinbase_tx5, tx6, coinbase_tx6] let page_request_forwards = PaginationRequest { cursor: response.cursor, results: 6, @@ -343,9 +343,9 @@ async fn get_transactions() { .map(|tx| tx.transaction.id(&ChainId::default())) .collect_vec(); // coinbase_tx4 - assert_eq!(transactions[1], tx4); + assert_eq!(transactions[0], tx4); // coinbase_tx5 - assert_eq!(transactions[3], tx5); + assert_eq!(transactions[2], tx5); // Check pagination state for middle page // it should have next and previous page assert!(response.has_next_page); @@ -357,11 +357,11 @@ async fn get_transactions() { .iter() .map(|tx| tx.transaction.id(&ChainId::default())) .collect_vec(); - // transactions[0] - coinbase_tx3 - assert_eq!(transactions[1], tx2); - // transactions[2] - coinbase_tx2 - assert_eq!(transactions[3], tx1); - // transactions[4] - coinbase_tx1 + assert_eq!(transactions[0], tx3); + // transactions[1] - coinbase_tx2 + assert_eq!(transactions[2], tx2); + // transactions[3] - coinbase_tx1 + assert_eq!(transactions[4], tx1); // Check pagination state for last page assert!(!response.has_next_page); assert!(response.has_previous_page); @@ -372,12 +372,12 @@ async fn get_transactions() { .iter() .map(|tx| tx.transaction.id(&ChainId::default())) .collect_vec(); + assert_eq!(transactions[0], tx4); // coinbase_tx4 - assert_eq!(transactions[1], tx4); + assert_eq!(transactions[2], tx5); // coinbase_tx5 - assert_eq!(transactions[3], tx5); + assert_eq!(transactions[4], tx6); // coinbase_tx6 - assert_eq!(transactions[5], tx6); // Check pagination state for last page assert!(!response.has_next_page); assert!(response.has_previous_page); @@ -530,7 +530,7 @@ async fn get_transactions_from_manual_blocks() { ) .unwrap(); - // Query for first 4: [coinbase_tx1, 0, 1, 2] + // Query for first 4: [0, 1, 2, 3] let page_request_forwards = PaginationRequest { cursor: None, results: 4, @@ -546,12 +546,12 @@ async fn get_transactions_from_manual_blocks() { .iter() .map(|tx| tx.transaction.id(&ChainId::default())) .collect_vec(); - // coinbase_tx1 - assert_eq!(transactions[1], txs[0].id(&ChainId::default())); - assert_eq!(transactions[2], txs[1].id(&ChainId::default())); - assert_eq!(transactions[3], txs[2].id(&ChainId::default())); + assert_eq!(transactions[0], txs[0].id(&ChainId::default())); + assert_eq!(transactions[1], txs[1].id(&ChainId::default())); + assert_eq!(transactions[2], txs[2].id(&ChainId::default())); + assert_eq!(transactions[3], txs[3].id(&ChainId::default())); - // Query forwards from last given cursor [2]: [3, 4, coinbase_tx2, 5, 6] + // Query forwards from last given cursor [2]: [4, coinbase_tx1, 5, 6, coinbase_tx2] let next_page_request_forwards = PaginationRequest { cursor: response.cursor, results: 5, @@ -567,13 +567,13 @@ async fn get_transactions_from_manual_blocks() { .iter() .map(|tx| tx.transaction.id(&ChainId::default())) .collect_vec(); - assert_eq!(transactions[0], txs[3].id(&ChainId::default())); - assert_eq!(transactions[1], txs[4].id(&ChainId::default())); + assert_eq!(transactions[0], txs[4].id(&ChainId::default())); + // coinbase_tx1 + assert_eq!(transactions[2], txs[5].id(&ChainId::default())); + assert_eq!(transactions[3], txs[6].id(&ChainId::default())); // coinbase_tx2 - assert_eq!(transactions[3], txs[5].id(&ChainId::default())); - assert_eq!(transactions[4], txs[6].id(&ChainId::default())); - // Query backwards from last given cursor [8]: [5, coinbase_tx2, 4, 3, 2, 1, 0, coinbase_tx1] + // Query backwards from last given cursor [8]: [6, 5, coinbase_tx1, 4, 3, 2, 1, 0] let page_request_backwards = PaginationRequest { cursor: response.cursor, results: 10, @@ -589,14 +589,14 @@ async fn get_transactions_from_manual_blocks() { .iter() .map(|tx| tx.transaction.id(&ChainId::default())) .collect_vec(); - assert_eq!(transactions[0], txs[5].id(&ChainId::default())); - // transactions[1] coinbase_tx2 - assert_eq!(transactions[2], txs[4].id(&ChainId::default())); - assert_eq!(transactions[3], txs[3].id(&ChainId::default())); - assert_eq!(transactions[4], txs[2].id(&ChainId::default())); - assert_eq!(transactions[5], txs[1].id(&ChainId::default())); - assert_eq!(transactions[6], txs[0].id(&ChainId::default())); - // transactions[7] coinbase_tx1 + assert_eq!(transactions[0], txs[6].id(&ChainId::default())); + assert_eq!(transactions[1], txs[5].id(&ChainId::default())); + // transactions[2] coinbase_tx1 + assert_eq!(transactions[3], txs[4].id(&ChainId::default())); + assert_eq!(transactions[4], txs[3].id(&ChainId::default())); + assert_eq!(transactions[5], txs[2].id(&ChainId::default())); + assert_eq!(transactions[6], txs[1].id(&ChainId::default())); + assert_eq!(transactions[7], txs[0].id(&ChainId::default())); } #[tokio::test] diff --git a/tests/tests/tx/predicates.rs b/tests/tests/tx/predicates.rs index 0824664f2aa..b9d8e6fbdc7 100644 --- a/tests/tests/tx/predicates.rs +++ b/tests/tests/tx/predicates.rs @@ -32,7 +32,7 @@ async fn transaction_with_valid_predicate_is_executed() { let asset_id = rng.gen(); // make predicate return 1 which mean valid let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); let mut predicate_tx = TransactionBuilder::script(Default::default(), Default::default()) .add_input(Input::coin_predicate( @@ -94,7 +94,7 @@ async fn transaction_with_invalid_predicate_is_rejected() { let asset_id = rng.gen(); // make predicate return 0 which means invalid let predicate = op::ret(RegId::ZERO).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); let predicate_tx = TransactionBuilder::script(Default::default(), Default::default()) .add_input(Input::coin_predicate( rng.gen(), @@ -130,7 +130,7 @@ async fn transaction_with_predicates_that_exhaust_gas_limit_are_rejected() { let asset_id = rng.gen(); // make predicate jump in infinite loop let predicate = op::jmp(RegId::ZERO).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); let predicate_tx = TransactionBuilder::script(Default::default(), Default::default()) .add_input(Input::coin_predicate( rng.gen(), diff --git a/tests/tests/tx/tx_pointer.rs b/tests/tests/tx/tx_pointer.rs index bd1c84b874e..3233215c933 100644 --- a/tests/tests/tx/tx_pointer.rs +++ b/tests/tests/tx/tx_pointer.rs @@ -164,7 +164,7 @@ async fn tx_pointer_set_from_previous_block() { let ret_tx2 = ret_tx2.as_script().unwrap(); // verify coin tx_pointer is correctly set - let expected_tx_pointer = TxPointer::new((block_height + 1u32).into(), 1); + let expected_tx_pointer = TxPointer::new((block_height + 1u32).into(), 0); assert_eq!( *ret_tx2.inputs()[0].tx_pointer().unwrap(), expected_tx_pointer diff --git a/tests/tests/tx/txn_status_subscription.rs b/tests/tests/tx/txn_status_subscription.rs index 903a86dc90b..4fdd338df92 100644 --- a/tests/tests/tx/txn_status_subscription.rs +++ b/tests/tests/tx/txn_status_subscription.rs @@ -53,7 +53,7 @@ async fn subscribe_txn_status() { .collect(); let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); // The third transaction needs to have a different input. let utxo_id = if i == 2 { 2 } else { 1 }; let utxo_id = UtxoId::new(Bytes32::from([utxo_id; 32]), 1); @@ -130,7 +130,7 @@ async fn test_regression_in_subscribe() { let mut config = Config::local_node(); config.utxo_validation = true; let predicate = op::ret(RegId::ONE).to_bytes().to_vec(); - let owner = Input::predicate_owner(&predicate, &ChainId::default()); + let owner = Input::predicate_owner(&predicate); let node = FuelService::new_node(config).await.unwrap(); let coin_pred = Input::coin_predicate( rng.gen(), diff --git a/tests/tests/tx/utxo_validation.rs b/tests/tests/tx/utxo_validation.rs index b759389621e..629de692a5b 100644 --- a/tests/tests/tx/utxo_validation.rs +++ b/tests/tests/tx/utxo_validation.rs @@ -300,7 +300,10 @@ async fn concurrent_tx_submission_produces_expected_blocks() { .results .iter() .flat_map(|b| { - b.transactions.iter().skip(1 /* coinbase */).copied() + b.transactions + .iter() + .take(b.transactions.len().saturating_sub(1) /* coinbase */) + .copied() }) .dedup_with_count() .map(|(count, id)| {