fuel_core/
executor.rs

1#[allow(clippy::arithmetic_side_effects)]
2#[allow(clippy::cast_possible_truncation)]
3#[allow(non_snake_case)]
4#[cfg(test)]
5mod tests {
6    #[cfg(not(feature = "wasm-executor"))]
7    use fuel_core_executor::{
8        executor::{
9            TimeoutOnlyTxWaiter,
10            TransparentPreconfirmationSender,
11            WaitNewTransactionsResult,
12        },
13        ports::{
14            NewTxWaiterPort,
15            PreconfirmationSenderPort,
16        },
17    };
18
19    #[cfg(not(feature = "wasm-executor"))]
20    use fuel_core_types::services::preconfirmation::{
21        Preconfirmation,
22        PreconfirmationStatus,
23    };
24
25    use crate as fuel_core;
26    use fuel_core::database::Database;
27    use fuel_core_executor::{
28        executor::OnceTransactionsSource,
29        ports::{
30            MaybeCheckedTransaction,
31            RelayerPort,
32        },
33        refs::ContractRef,
34    };
35    use fuel_core_storage::{
36        Result as StorageResult,
37        StorageAsMut,
38        StorageAsRef,
39        tables::{
40            Coins,
41            ConsensusParametersVersions,
42            ContractsRawCode,
43            Messages,
44        },
45        transactional::{
46            AtomicView,
47            WriteTransaction,
48        },
49    };
50    use fuel_core_types::{
51        blockchain::{
52            block::{
53                Block,
54                PartialFuelBlock,
55            },
56            header::{
57                ApplicationHeader,
58                ConsensusHeader,
59                PartialBlockHeader,
60            },
61            primitives::DaBlockHeight,
62        },
63        entities::{
64            coins::coin::CompressedCoin,
65            relayer::message::{
66                Message,
67                MessageV1,
68            },
69        },
70        fuel_asm::{
71            GTFArgs,
72            RegId,
73            op,
74        },
75        fuel_crypto::SecretKey,
76        fuel_merkle::common::empty_sum_sha256,
77        fuel_tx::{
78            Bytes32,
79            ConsensusParameters,
80            Create,
81            DependentCost,
82            FeeParameters,
83            Finalizable,
84            GasCostsValues,
85            Output,
86            Receipt,
87            Script,
88            Transaction,
89            TransactionBuilder,
90            TransactionFee,
91            TxParameters,
92            TxPointer,
93            UniqueIdentifier,
94            UtxoId,
95            ValidityError,
96            consensus_parameters::gas::GasCostsValuesV1,
97            field::{
98                Expiration,
99                InputContract,
100                Inputs,
101                MintAmount,
102                MintAssetId,
103                MintGasPrice,
104                OutputContract,
105                Outputs,
106                Policies,
107                TxPointer as TxPointerTraitTrait,
108            },
109            input::{
110                Input,
111                coin::{
112                    CoinPredicate,
113                    CoinSigned,
114                },
115                contract,
116            },
117            policies::PolicyType,
118        },
119        fuel_types::{
120            Address,
121            AssetId,
122            BlockHeight,
123            ChainId,
124            ContractId,
125            Word,
126            canonical::Serialize,
127        },
128        fuel_vm::{
129            Call,
130            CallFrame,
131            Contract,
132            checked_transaction::{
133                CheckError,
134                EstimatePredicates,
135                IntoChecked,
136            },
137            interpreter::{
138                ExecutableTransaction,
139                MemoryInstance,
140            },
141            predicate::EmptyStorage,
142            script_with_data_offset,
143            util::test_helpers::TestBuilder as TxBuilder,
144        },
145        services::{
146            block_producer::Components,
147            executor::{
148                Error as ExecutorError,
149                Event as ExecutorEvent,
150                ExecutionResult,
151                TransactionExecutionResult,
152                TransactionValidityError,
153            },
154            relayer::Event,
155        },
156        tai64::Tai64,
157        test_helpers::create_contract,
158    };
159    use fuel_core_upgradable_executor::executor::Executor;
160    use itertools::Itertools;
161    use rand::{
162        Rng,
163        SeedableRng,
164        prelude::StdRng,
165    };
166    use sha2::{
167        Digest,
168        Sha256,
169    };
170
171    #[derive(Clone, Debug, Default)]
172    struct Config {
173        /// Network-wide common parameters used for validating the chain.
174        /// The executor already has these parameters, and this field allows us
175        /// to override the existing value.
176        pub consensus_parameters: ConsensusParameters,
177        /// Default mode for `forbid_fake_coins` in the executor.
178        pub forbid_fake_coins_default: bool,
179    }
180
181    #[derive(Clone, Debug)]
182    struct DisabledRelayer;
183
184    impl RelayerPort for DisabledRelayer {
185        fn enabled(&self) -> bool {
186            false
187        }
188
189        fn get_events(&self, _: &DaBlockHeight) -> anyhow::Result<Vec<Event>> {
190            unimplemented!()
191        }
192    }
193
194    impl AtomicView for DisabledRelayer {
195        type LatestView = Self;
196
197        fn latest_view(&self) -> StorageResult<Self::LatestView> {
198            Ok(self.clone())
199        }
200    }
201
202    fn add_consensus_parameters(
203        mut database: Database,
204        consensus_parameters: &ConsensusParameters,
205    ) -> Database {
206        // Set the consensus parameters for the executor.
207        let mut tx = database.write_transaction();
208        tx.storage_as_mut::<ConsensusParametersVersions>()
209            .insert(&0, consensus_parameters)
210            .unwrap();
211        tx.commit().unwrap();
212        database
213    }
214
215    fn create_executor(
216        database: Database,
217        config: Config,
218    ) -> Executor<Database, DisabledRelayer> {
219        let executor_config = fuel_core_upgradable_executor::config::Config {
220            forbid_fake_coins_default: config.forbid_fake_coins_default,
221            allow_syscall: true,
222            native_executor_version: None,
223            allow_historical_execution: true,
224        };
225
226        let database = add_consensus_parameters(database, &config.consensus_parameters);
227
228        Executor::new(database, DisabledRelayer, executor_config)
229    }
230
231    pub(crate) fn setup_executable_script() -> (Create, Script) {
232        let mut rng = StdRng::seed_from_u64(2322);
233        let asset_id: AssetId = rng.r#gen();
234        let owner: Address = rng.r#gen();
235        let input_amount = 1000;
236        let variable_transfer_amount = 100;
237        let coin_output_amount = 150;
238
239        let (create, contract_id) = create_contract(
240            vec![
241                // load amount of coins to 0x10
242                op::addi(0x10, RegId::FP, CallFrame::a_offset().try_into().unwrap()),
243                op::lw(0x10, 0x10, 0),
244                // load asset id to 0x11
245                op::addi(0x11, RegId::FP, CallFrame::b_offset().try_into().unwrap()),
246                op::lw(0x11, 0x11, 0),
247                // load address to 0x12
248                op::addi(0x12, 0x11, 32),
249                // load output index (0) to 0x13
250                op::addi(0x13, RegId::ZERO, 0),
251                op::tro(0x12, 0x13, 0x10, 0x11),
252                op::ret(RegId::ONE),
253            ]
254            .into_iter()
255            .collect::<Vec<u8>>()
256            .as_slice(),
257            &mut rng,
258        );
259        let (script, data_offset) = script_with_data_offset!(
260            data_offset,
261            vec![
262                // set reg 0x10 to call data
263                op::movi(0x10, data_offset + 64),
264                // set reg 0x11 to asset id
265                op::movi(0x11, data_offset),
266                // set reg 0x12 to call amount
267                op::movi(0x12, variable_transfer_amount),
268                // call contract without any tokens to transfer in (3rd arg arbitrary when 2nd is zero)
269                op::call(0x10, 0x12, 0x11, RegId::CGAS),
270                op::ret(RegId::ONE),
271            ],
272            TxParameters::DEFAULT.tx_offset()
273        );
274
275        let script_data: Vec<u8> = [
276            asset_id.as_ref(),
277            owner.as_ref(),
278            Call::new(
279                contract_id,
280                variable_transfer_amount as Word,
281                data_offset as Word,
282            )
283            .to_bytes()
284            .as_ref(),
285        ]
286        .into_iter()
287        .flatten()
288        .copied()
289        .collect();
290
291        let script = TxBuilder::new(2322)
292            .script_gas_limit(TxParameters::DEFAULT.max_gas_per_tx() >> 1)
293            .start_script(script, script_data)
294            .contract_input(contract_id)
295            .coin_input(asset_id, input_amount)
296            .variable_output(Default::default())
297            .coin_output(asset_id, coin_output_amount)
298            .change_output(asset_id)
299            .contract_output(&contract_id)
300            .build()
301            .transaction()
302            .clone();
303
304        (create, script)
305    }
306
307    pub(crate) fn test_block(
308        block_height: BlockHeight,
309        da_block_height: DaBlockHeight,
310        num_txs: usize,
311    ) -> Block {
312        let transactions = (1..num_txs + 1).map(script_tx_for_amount).collect_vec();
313
314        let mut block = Block::default();
315        block.header_mut().set_block_height(block_height);
316        block.header_mut().set_da_height(da_block_height);
317        *block.transactions_mut() = transactions;
318        block
319    }
320
321    fn script_tx_for_amount(amount: usize) -> Transaction {
322        let asset = AssetId::BASE;
323        TxBuilder::new(2322u64)
324            .script_gas_limit(10)
325            .coin_input(asset, (amount as Word) * 100)
326            .coin_output(asset, (amount as Word) * 50)
327            .change_output(asset)
328            .build()
329            .transaction()
330            .to_owned()
331            .into()
332    }
333
334    // Happy path test case that a produced block will also validate
335    #[test]
336    fn executor_validates_correctly_produced_block() {
337        let mut producer = create_executor(Default::default(), Default::default());
338        let verifier = create_executor(Default::default(), Default::default());
339        let block = test_block(1u32.into(), 0u64.into(), 10);
340
341        let ExecutionResult {
342            block,
343            skipped_transactions,
344            ..
345        } = producer.produce_and_commit(block.into()).unwrap();
346
347        let validation_result = verifier.validate(&block);
348        assert!(validation_result.is_ok());
349        assert!(skipped_transactions.is_empty());
350    }
351
352    // Ensure transaction commitment != default after execution
353    #[test]
354    fn executor_commits_transactions_to_block() {
355        let mut producer = create_executor(Default::default(), Default::default());
356        let block = test_block(1u32.into(), 0u64.into(), 10);
357        let start_block = block.clone();
358
359        let ExecutionResult {
360            block,
361            skipped_transactions,
362            ..
363        } = producer.produce_and_commit(block.into()).unwrap();
364
365        assert!(skipped_transactions.is_empty());
366        assert_ne!(
367            start_block.header().transactions_root(),
368            block.header().transactions_root()
369        );
370        assert_eq!(block.transactions().len(), 11);
371        assert!(block.transactions()[10].as_mint().is_some());
372        if let Some(mint) = block.transactions()[10].as_mint() {
373            assert_eq!(
374                mint.tx_pointer(),
375                &TxPointer::new(*block.header().height(), 10)
376            );
377            assert_eq!(mint.mint_asset_id(), &AssetId::BASE);
378            assert_eq!(mint.mint_amount(), &0);
379            assert_eq!(mint.input_contract().contract_id, ContractId::zeroed());
380            assert_eq!(mint.input_contract().balance_root, Bytes32::zeroed());
381            assert_eq!(mint.input_contract().state_root, Bytes32::zeroed());
382            assert_eq!(mint.input_contract().utxo_id, UtxoId::default());
383            assert_eq!(mint.input_contract().tx_pointer, TxPointer::default());
384            assert_eq!(mint.output_contract().balance_root, Bytes32::zeroed());
385            assert_eq!(mint.output_contract().state_root, Bytes32::zeroed());
386            assert_eq!(mint.output_contract().input_index, 0);
387        } else {
388            panic!("Invalid outputs of coinbase");
389        }
390    }
391
392    mod coinbase {
393        use crate::graphql_api::ports::DatabaseContracts;
394
395        use super::*;
396        use fuel_core_storage::{
397            iter::IterDirection,
398            transactional::{
399                AtomicView,
400                Modifiable,
401            },
402        };
403        use fuel_core_types::services::graphql_api::ContractBalance;
404
405        #[test]
406        fn executor_commits_transactions_with_non_zero_coinbase_generation() {
407            // The test verifies the correctness of the coinbase contract update.
408            // The test generates two blocks with a non-zero fee.
409            //
410            // The first block contains one valid and one invalid transaction.
411            // This part of the test verifies that the invalid transaction doesn't influence
412            // the final fee, and the final is the same as the `max_fee` of the valid transaction.
413            //
414            // The second block contains only a valid transaction, and it uses
415            // the `Mint` transaction from the first block to validate the contract
416            // state transition between blocks.
417            let price = 1;
418            let amount = 10000;
419            let limit = 0;
420            let gas_price_factor = 1;
421            let script = TxBuilder::new(1u64)
422                .script_gas_limit(limit)
423                .max_fee_limit(amount)
424                .coin_input(AssetId::BASE, amount)
425                .change_output(AssetId::BASE)
426                .build()
427                .transaction()
428                .clone();
429
430            let recipient = Contract::EMPTY_CONTRACT_ID;
431
432            let fee_params =
433                FeeParameters::default().with_gas_price_factor(gas_price_factor);
434            let mut consensus_parameters = ConsensusParameters::default();
435            consensus_parameters.set_fee_params(fee_params);
436            let config = Config {
437                consensus_parameters: consensus_parameters.clone(),
438                ..Default::default()
439            };
440
441            let database = &mut Database::default();
442            database
443                .storage::<ContractsRawCode>()
444                .insert(&recipient, &[])
445                .expect("Should insert coinbase contract");
446
447            let mut producer = create_executor(database.clone(), config);
448
449            let expected_fee_amount_1 = TransactionFee::checked_from_tx(
450                consensus_parameters.gas_costs(),
451                consensus_parameters.fee_params(),
452                &script,
453                price,
454            )
455            .unwrap()
456            .max_fee();
457            let invalid_duplicate_tx = script.clone().into();
458
459            let mut header = PartialBlockHeader::default();
460            header.consensus.height = 1.into();
461
462            let (
463                ExecutionResult {
464                    block,
465                    skipped_transactions,
466                    ..
467                },
468                changes,
469            ) = producer
470                .produce_without_commit_with_source_direct_resolve(Components {
471                    header_to_produce: header,
472                    transactions_source: OnceTransactionsSource::new(vec![
473                        script.into(),
474                        invalid_duplicate_tx,
475                    ]),
476                    gas_price: price,
477                    coinbase_recipient: recipient,
478                })
479                .unwrap()
480                .into();
481            producer
482                .storage_view_provider
483                .commit_changes(changes)
484                .unwrap();
485
486            assert_eq!(skipped_transactions.len(), 1);
487            assert_eq!(block.transactions().len(), 2);
488            assert!(expected_fee_amount_1 > 0);
489            let first_mint;
490
491            let mut h = Sha256::new();
492            h.update(consensus_parameters.base_asset_id().as_ref());
493            h.update([0u8]);
494            let input_balances_hash = Bytes32::new(h.finalize().into());
495
496            let mut h = Sha256::new();
497            h.update(consensus_parameters.base_asset_id().as_ref());
498            h.update([1u8]);
499            h.update(expected_fee_amount_1.to_be_bytes());
500            let output_balances_hash = Bytes32::new(h.finalize().into());
501
502            let empty_hash = Bytes32::zeroed();
503
504            if let Some(mint) = block.transactions()[1].as_mint() {
505                assert_eq!(
506                    mint.tx_pointer(),
507                    &TxPointer::new(*block.header().height(), 1)
508                );
509                assert_eq!(mint.mint_asset_id(), &AssetId::BASE);
510                assert_eq!(mint.mint_amount(), &expected_fee_amount_1);
511                assert_eq!(mint.input_contract().contract_id, recipient);
512                assert_eq!(mint.input_contract().balance_root, input_balances_hash);
513                assert_eq!(mint.input_contract().state_root, empty_hash);
514                assert_eq!(mint.input_contract().utxo_id, UtxoId::default());
515                assert_eq!(mint.input_contract().tx_pointer, TxPointer::default());
516                assert_eq!(mint.output_contract().balance_root, output_balances_hash);
517                assert_eq!(mint.output_contract().state_root, empty_hash);
518                assert_eq!(mint.output_contract().input_index, 0);
519                first_mint = mint.clone();
520            } else {
521                panic!("Invalid coinbase transaction");
522            }
523
524            let ContractBalance {
525                asset_id, amount, ..
526            } = producer
527                .storage_view_provider
528                .latest_view()
529                .unwrap()
530                .contract_balances(recipient, None, IterDirection::Forward)
531                .next()
532                .unwrap()
533                .unwrap();
534            assert_eq!(asset_id, AssetId::zeroed());
535            assert_eq!(amount, expected_fee_amount_1);
536
537            let script = TxBuilder::new(2u64)
538                .script_gas_limit(limit)
539                .max_fee_limit(amount)
540                .coin_input(AssetId::BASE, amount)
541                .change_output(AssetId::BASE)
542                .build()
543                .transaction()
544                .clone();
545
546            let expected_fee_amount_2 = TransactionFee::checked_from_tx(
547                consensus_parameters.gas_costs(),
548                consensus_parameters.fee_params(),
549                &script,
550                price,
551            )
552            .unwrap()
553            .max_fee();
554
555            let mut header = PartialBlockHeader::default();
556            header.consensus.height = 2.into();
557
558            let (
559                ExecutionResult {
560                    block,
561                    skipped_transactions,
562                    ..
563                },
564                changes,
565            ) = producer
566                .produce_without_commit_with_source_direct_resolve(Components {
567                    header_to_produce: header,
568                    transactions_source: OnceTransactionsSource::new(vec![script.into()]),
569                    gas_price: price,
570                    coinbase_recipient: recipient,
571                })
572                .unwrap()
573                .into();
574            producer
575                .storage_view_provider
576                .commit_changes(changes)
577                .unwrap();
578
579            assert_eq!(skipped_transactions.len(), 0);
580            assert_eq!(block.transactions().len(), 2);
581
582            if let Some(second_mint) = block.transactions()[1].as_mint() {
583                assert_eq!(second_mint.tx_pointer(), &TxPointer::new(2.into(), 1));
584                assert_eq!(second_mint.mint_asset_id(), &AssetId::BASE);
585                assert_eq!(second_mint.mint_amount(), &expected_fee_amount_2);
586                assert_eq!(second_mint.input_contract().contract_id, recipient);
587                assert_eq!(
588                    second_mint.input_contract().utxo_id,
589                    UtxoId::new(first_mint.id(&consensus_parameters.chain_id()), 0)
590                );
591                assert_eq!(
592                    second_mint.input_contract().tx_pointer,
593                    TxPointer::new(1.into(), 1)
594                );
595                assert_eq!(second_mint.output_contract().input_index, 0);
596            } else {
597                panic!("Invalid coinbase transaction");
598            }
599            let ContractBalance {
600                asset_id, amount, ..
601            } = producer
602                .storage_view_provider
603                .latest_view()
604                .unwrap()
605                .contract_balances(recipient, None, IterDirection::Forward)
606                .next()
607                .unwrap()
608                .unwrap();
609
610            assert_eq!(asset_id, AssetId::zeroed());
611            assert_eq!(amount, expected_fee_amount_1 + expected_fee_amount_2);
612        }
613
614        #[test]
615        fn skip_coinbase_during_dry_run() {
616            let price = 1;
617            let limit = 0;
618            let gas_price_factor = 1;
619            let script = TxBuilder::new(2322u64)
620                .script_gas_limit(limit)
621                // Set a price for the test
622                .gas_price(price)
623                .coin_input(AssetId::BASE, 10000)
624                .change_output(AssetId::BASE)
625                .build()
626                .transaction()
627                .clone();
628
629            let fee_params =
630                FeeParameters::default().with_gas_price_factor(gas_price_factor);
631            let mut consensus_parameters = ConsensusParameters::default();
632            consensus_parameters.set_fee_params(fee_params);
633            let config = Config {
634                consensus_parameters,
635                ..Default::default()
636            };
637            let recipient = [1u8; 32].into();
638
639            let producer = create_executor(Default::default(), config);
640
641            let result = producer
642                .dry_run_without_commit_with_source(Components {
643                    header_to_produce: Default::default(),
644                    transactions_source: OnceTransactionsSource::new(vec![script.into()]),
645                    coinbase_recipient: recipient,
646                    gas_price: 0,
647                })
648                .unwrap();
649            let ExecutionResult { block, .. } = result.into_result();
650
651            assert_eq!(block.transactions().len(), 1);
652        }
653
654        #[test]
655        fn executor_commits_transactions_with_non_zero_coinbase_validation() {
656            let price = 1;
657            let amount = 10000;
658            let limit = 0;
659            let gas_price_factor = 1;
660            let script = TxBuilder::new(2322u64)
661                .script_gas_limit(limit)
662                .max_fee_limit(amount)
663                .coin_input(AssetId::BASE, 10000)
664                .change_output(AssetId::BASE)
665                .build()
666                .transaction()
667                .clone();
668            let recipient = Contract::EMPTY_CONTRACT_ID;
669
670            let fee_params =
671                FeeParameters::default().with_gas_price_factor(gas_price_factor);
672            let mut consensus_parameters = ConsensusParameters::default();
673            consensus_parameters.set_fee_params(fee_params);
674            let config = Config {
675                consensus_parameters,
676                ..Default::default()
677            };
678            let database = &mut Database::default();
679
680            database
681                .storage::<ContractsRawCode>()
682                .insert(&recipient, &[])
683                .expect("Should insert coinbase contract");
684
685            let producer = create_executor(database.clone(), config.clone());
686
687            let ExecutionResult {
688                block,
689                skipped_transactions,
690                ..
691            } = producer
692                .produce_without_commit_with_source_direct_resolve(Components {
693                    header_to_produce: PartialBlockHeader::default(),
694                    transactions_source: OnceTransactionsSource::new(vec![script.into()]),
695                    gas_price: price,
696                    coinbase_recipient: recipient,
697                })
698                .unwrap()
699                .into_result();
700            assert!(skipped_transactions.is_empty());
701            let produced_txs = block.transactions().to_vec();
702
703            let mut validator = create_executor(
704                Default::default(),
705                // Use the same config as block producer
706                config,
707            );
708            let _ = validator.validate_and_commit(&block).unwrap();
709            assert_eq!(block.transactions(), produced_txs);
710            let ContractBalance {
711                asset_id, amount, ..
712            } = validator
713                .storage_view_provider
714                .latest_view()
715                .unwrap()
716                .contract_balances(recipient, None, IterDirection::Forward)
717                .next()
718                .unwrap()
719                .unwrap();
720            assert_eq!(asset_id, AssetId::zeroed());
721            assert_ne!(amount, 0);
722        }
723
724        #[test]
725        fn execute_cb_command() {
726            fn compare_coinbase_addresses(
727                config_coinbase: ContractId,
728                expected_in_tx_coinbase: ContractId,
729            ) -> bool {
730                let script = TxBuilder::new(2322u64)
731                    .script_gas_limit(100000)
732                    // Set a price for the test
733                    .gas_price(0)
734                    .start_script(vec![
735                        // Store the size of the `Address`(32 bytes) into register `0x11`.
736                        op::movi(0x11, Address::LEN.try_into().unwrap()),
737                        // Allocate 32 bytes on the heap.
738                        op::aloc(0x11),
739                        // Store the pointer to the beginning of the free memory into
740                        // register `0x10`.
741                        op::move_(0x10, RegId::HP),
742                        // Store `config_coinbase` `Address` into MEM[$0x10; 32].
743                        op::cb(0x10),
744                        // Store the pointer on the beginning of script data into register `0x12`.
745                        // Script data contains `expected_in_tx_coinbase` - 32 bytes of data.
746                        op::gtf_args(0x12, 0x00, GTFArgs::ScriptData),
747                        // Compare retrieved `config_coinbase`(register `0x10`) with
748                        // passed `expected_in_tx_coinbase`(register `0x12`) where the length
749                        // of memory comparison is 32 bytes(register `0x11`) and store result into
750                        // register `0x13`(1 - true, 0 - false).
751                        op::meq(0x13, 0x10, 0x12, 0x11),
752                        // Return the result of the comparison as a receipt.
753                        op::ret(0x13),
754                    ], expected_in_tx_coinbase.to_vec() /* pass expected address as script data */)
755                    .coin_input(AssetId::BASE, 1000)
756                    .variable_output(Default::default())
757                    .coin_output(AssetId::BASE, 1000)
758                    .change_output(AssetId::BASE)
759                    .build()
760                    .transaction()
761                    .clone();
762
763                let mut producer =
764                    create_executor(Default::default(), Default::default());
765
766                let mut block = Block::default();
767                *block.transactions_mut() = vec![script.clone().into()];
768
769                let (ExecutionResult { tx_status, .. }, changes) = producer
770                    .produce_without_commit_with_coinbase(
771                        block.into(),
772                        config_coinbase,
773                        0,
774                    )
775                    .expect("Should execute the block")
776                    .into();
777                producer
778                    .storage_view_provider
779                    .commit_changes(changes)
780                    .unwrap();
781                let receipts = tx_status[0].result.receipts();
782
783                if let Some(Receipt::Return { val, .. }) = receipts.first() {
784                    *val == 1
785                } else {
786                    panic!("Execution of the `CB` script failed failed")
787                }
788            }
789
790            assert!(compare_coinbase_addresses(
791                ContractId::from([1u8; 32]),
792                ContractId::from([1u8; 32]),
793            ));
794            assert!(!compare_coinbase_addresses(
795                ContractId::from([9u8; 32]),
796                ContractId::from([1u8; 32]),
797            ));
798            assert!(!compare_coinbase_addresses(
799                ContractId::from([1u8; 32]),
800                ContractId::from([9u8; 32]),
801            ));
802            assert!(compare_coinbase_addresses(
803                ContractId::from([9u8; 32]),
804                ContractId::from([9u8; 32]),
805            ));
806        }
807
808        #[test]
809        fn invalidate_unexpected_index() {
810            let mint = Transaction::mint(
811                TxPointer::new(Default::default(), 1),
812                Default::default(),
813                Default::default(),
814                Default::default(),
815                Default::default(),
816                Default::default(),
817            );
818
819            let mut block = Block::default();
820            *block.transactions_mut() = vec![mint.into()];
821            block.header_mut().recalculate_metadata();
822
823            let mut validator = create_executor(
824                Default::default(),
825                Config {
826                    forbid_fake_coins_default: false,
827                    ..Default::default()
828                },
829            );
830            let validation_err = validator
831                .validate_and_commit(&block)
832                .expect_err("Expected error because coinbase if invalid");
833            assert!(matches!(
834                validation_err,
835                ExecutorError::MintHasUnexpectedIndex
836            ));
837        }
838
839        #[test]
840        fn invalidate_is_not_last() {
841            let mint = Transaction::mint(
842                TxPointer::new(Default::default(), 0),
843                Default::default(),
844                Default::default(),
845                Default::default(),
846                Default::default(),
847                Default::default(),
848            );
849            let tx = Transaction::default_test_tx();
850
851            let mut block = Block::default();
852            *block.transactions_mut() = vec![mint.clone().into(), tx, mint.into()];
853            block.header_mut().recalculate_metadata();
854
855            let mut validator = create_executor(Default::default(), Default::default());
856            let validation_err = validator
857                .validate_and_commit(&block)
858                .expect_err("Expected error because coinbase if invalid");
859            assert!(matches!(
860                validation_err,
861                ExecutorError::MintIsNotLastTransaction
862            ));
863        }
864
865        #[test]
866        fn invalidate_block_missed_coinbase() {
867            let block = Block::default();
868
869            let mut validator = create_executor(Default::default(), Default::default());
870            let validation_err = validator
871                .validate_and_commit(&block)
872                .expect_err("Expected error because coinbase is missing");
873            assert!(matches!(validation_err, ExecutorError::MintMissing));
874        }
875
876        #[test]
877        fn invalidate_block_height() {
878            let mint = Transaction::mint(
879                TxPointer::new(1.into(), Default::default()),
880                Default::default(),
881                Default::default(),
882                Default::default(),
883                Default::default(),
884                Default::default(),
885            );
886
887            let mut block = Block::default();
888            *block.transactions_mut() = vec![mint.into()];
889            block.header_mut().recalculate_metadata();
890
891            let mut validator = create_executor(Default::default(), Default::default());
892            let validation_err = validator
893                .validate_and_commit(&block)
894                .expect_err("Expected error because coinbase if invalid");
895
896            assert!(matches!(
897                validation_err,
898                ExecutorError::InvalidTransaction(CheckError::Validity(
899                    ValidityError::TransactionMintIncorrectBlockHeight
900                ))
901            ));
902        }
903
904        #[test]
905        fn invalidate_invalid_base_asset() {
906            let mint = Transaction::mint(
907                TxPointer::new(Default::default(), Default::default()),
908                Default::default(),
909                Default::default(),
910                Default::default(),
911                Default::default(),
912                Default::default(),
913            );
914
915            let mut block = Block::default();
916            *block.transactions_mut() = vec![mint.into()];
917            block.header_mut().recalculate_metadata();
918
919            let mut consensus_parameters = ConsensusParameters::default();
920            consensus_parameters.set_base_asset_id([1u8; 32].into());
921
922            let config = Config {
923                consensus_parameters,
924                ..Default::default()
925            };
926            let mut validator = create_executor(Default::default(), config);
927            let validation_err = validator
928                .validate_and_commit(&block)
929                .expect_err("Expected error because coinbase if invalid");
930
931            assert!(matches!(
932                validation_err,
933                ExecutorError::InvalidTransaction(CheckError::Validity(
934                    ValidityError::TransactionMintNonBaseAsset
935                ))
936            ));
937        }
938
939        #[test]
940        fn invalidate_mismatch_amount() {
941            let mint = Transaction::mint(
942                TxPointer::new(Default::default(), Default::default()),
943                Default::default(),
944                Default::default(),
945                123,
946                Default::default(),
947                Default::default(),
948            );
949
950            let mut block = Block::default();
951            *block.transactions_mut() = vec![mint.into()];
952            block.header_mut().recalculate_metadata();
953
954            let mut validator = create_executor(Default::default(), Default::default());
955            let validation_err = validator
956                .validate_and_commit(&block)
957                .expect_err("Expected error because coinbase if invalid");
958            assert!(matches!(
959                validation_err,
960                ExecutorError::CoinbaseAmountMismatch
961            ));
962        }
963    }
964
965    #[test]
966    fn executor_invalidates_expired_tx() {
967        let producer = create_executor(Default::default(), Default::default());
968        let validator = create_executor(Default::default(), Default::default());
969
970        // Given
971        let mut block = test_block(2u32.into(), 0u64.into(), 0);
972
973        let amount = 1;
974        let asset = AssetId::BASE;
975        let mut tx = TxBuilder::new(2322u64)
976            .script_gas_limit(10)
977            .coin_input(asset, (amount as Word) * 100)
978            .coin_output(asset, (amount as Word) * 50)
979            .change_output(asset)
980            .build()
981            .transaction()
982            .clone();
983
984        // When
985        tx.set_expiration(1u32.into());
986        block.transactions_mut().push(tx.clone().into());
987
988        let ExecutionResult {
989            skipped_transactions,
990            mut block,
991            ..
992        } = producer
993            .produce_without_commit(block.into())
994            .unwrap()
995            .into_result();
996
997        // Then
998        assert_eq!(skipped_transactions.len(), 1);
999        assert_eq!(
1000            skipped_transactions[0].1,
1001            ExecutorError::InvalidTransaction(CheckError::Validity(
1002                ValidityError::TransactionExpiration
1003            ))
1004        );
1005
1006        // Produced block is valid
1007        let _ = validator.validate(&block).unwrap().into_result();
1008
1009        // Make the block invalid by adding expired transaction
1010        let len = block.transactions().len();
1011        block.transactions_mut().insert(len - 1, tx.into());
1012
1013        let verify_error = validator.validate(&block).unwrap_err();
1014        assert_eq!(
1015            verify_error,
1016            ExecutorError::InvalidTransaction(CheckError::Validity(
1017                ValidityError::TransactionExpiration
1018            ))
1019        );
1020    }
1021
1022    // Ensure tx has at least one input to cover gas
1023    #[test]
1024    fn executor_invalidates_missing_gas_input() {
1025        let mut rng = StdRng::seed_from_u64(2322u64);
1026        let consensus_parameters = ConsensusParameters::default();
1027        let config = Config {
1028            consensus_parameters: consensus_parameters.clone(),
1029            ..Default::default()
1030        };
1031        let producer = create_executor(Default::default(), config.clone());
1032
1033        let verifier = create_executor(Default::default(), config);
1034
1035        let gas_limit = 100;
1036        let max_fee = 1;
1037        let script = TransactionBuilder::script(vec![], vec![])
1038            .add_unsigned_coin_input(
1039                SecretKey::random(&mut rng),
1040                rng.r#gen(),
1041                rng.r#gen(),
1042                rng.r#gen(),
1043                Default::default(),
1044            )
1045            .script_gas_limit(gas_limit)
1046            .max_fee_limit(max_fee)
1047            .finalize();
1048        let tx: Transaction = script.into();
1049
1050        let block = PartialFuelBlock {
1051            header: Default::default(),
1052            transactions: vec![tx.clone()],
1053        };
1054
1055        let ExecutionResult {
1056            skipped_transactions,
1057            mut block,
1058            ..
1059        } = producer
1060            .produce_without_commit(block)
1061            .unwrap()
1062            .into_result();
1063        let produce_result = &skipped_transactions[0].1;
1064        assert!(matches!(
1065            produce_result,
1066            &ExecutorError::InvalidTransaction(
1067                CheckError::Validity(
1068                    ValidityError::InsufficientFeeAmount { expected, .. }
1069                )
1070            ) if expected == max_fee
1071        ));
1072
1073        // Produced block is valid
1074        let _ = verifier.validate(&block).unwrap().into_result();
1075
1076        // Invalidate the block with Insufficient tx
1077        let len = block.transactions().len();
1078        block.transactions_mut().insert(len - 1, tx);
1079        let verify_result = verifier.validate(&block);
1080        assert!(matches!(
1081            verify_result,
1082            Err(ExecutorError::InvalidTransaction(
1083                CheckError::Validity(
1084                    ValidityError::InsufficientFeeAmount { expected, .. }
1085                )
1086            )) if expected == max_fee
1087        ))
1088    }
1089
1090    #[test]
1091    fn executor_invalidates_duplicate_tx_id() {
1092        let producer = create_executor(Default::default(), Default::default());
1093
1094        let verifier = create_executor(Default::default(), Default::default());
1095
1096        let block = PartialFuelBlock {
1097            header: Default::default(),
1098            transactions: vec![
1099                Transaction::default_test_tx(),
1100                Transaction::default_test_tx(),
1101            ],
1102        };
1103
1104        let ExecutionResult {
1105            skipped_transactions,
1106            mut block,
1107            ..
1108        } = producer
1109            .produce_without_commit(block)
1110            .unwrap()
1111            .into_result();
1112        let produce_result = &skipped_transactions[0].1;
1113        assert!(matches!(
1114            produce_result,
1115            &ExecutorError::TransactionIdCollision(_)
1116        ));
1117
1118        // Produced block is valid
1119        let _ = verifier.validate(&block).unwrap().into_result();
1120
1121        // Make the block invalid by adding of the duplicating transaction
1122        let len = block.transactions().len();
1123        block
1124            .transactions_mut()
1125            .insert(len - 1, Transaction::default_test_tx());
1126        let verify_result = verifier.validate(&block);
1127        assert!(matches!(
1128            verify_result,
1129            Err(ExecutorError::TransactionIdCollision(_))
1130        ));
1131    }
1132
1133    // invalidate a block if a tx input doesn't exist
1134    #[test]
1135    fn executor_invalidates_missing_inputs() {
1136        // create an input which doesn't exist in the utxo set
1137        let mut rng = StdRng::seed_from_u64(2322u64);
1138
1139        let tx = TransactionBuilder::script(
1140            vec![op::ret(RegId::ONE)].into_iter().collect(),
1141            vec![],
1142        )
1143        .add_unsigned_coin_input(
1144            SecretKey::random(&mut rng),
1145            rng.r#gen(),
1146            10,
1147            Default::default(),
1148            Default::default(),
1149        )
1150        .add_output(Output::Change {
1151            to: Default::default(),
1152            amount: 0,
1153            asset_id: Default::default(),
1154        })
1155        .finalize_as_transaction();
1156
1157        // setup executors with utxo-validation enabled
1158        let config = Config {
1159            forbid_fake_coins_default: true,
1160            ..Default::default()
1161        };
1162        let producer = create_executor(Database::default(), config.clone());
1163
1164        let verifier = create_executor(Default::default(), config);
1165
1166        let block = PartialFuelBlock {
1167            header: Default::default(),
1168            transactions: vec![tx.clone()],
1169        };
1170
1171        let ExecutionResult {
1172            skipped_transactions,
1173            mut block,
1174            ..
1175        } = producer
1176            .produce_without_commit(block)
1177            .unwrap()
1178            .into_result();
1179        let produce_result = &skipped_transactions[0].1;
1180        assert!(matches!(
1181            produce_result,
1182            &ExecutorError::TransactionValidity(
1183                TransactionValidityError::CoinDoesNotExist(_)
1184            )
1185        ));
1186
1187        // Produced block is valid
1188        let _ = verifier.validate(&block).unwrap().into_result();
1189
1190        // Invalidate block by adding transaction with not existing coin
1191        let len = block.transactions().len();
1192        block.transactions_mut().insert(len - 1, tx);
1193        let verify_result = verifier.validate(&block);
1194        assert!(matches!(
1195            verify_result,
1196            Err(ExecutorError::TransactionValidity(
1197                TransactionValidityError::CoinDoesNotExist(_)
1198            ))
1199        ));
1200    }
1201
1202    // corrupt a produced block by randomizing change amount
1203    // and verify that the executor invalidates the tx
1204    #[test]
1205    fn executor_invalidates_blocks_with_diverging_tx_outputs() {
1206        let input_amount = 10;
1207        let fake_output_amount = 100;
1208
1209        let tx: Transaction = TxBuilder::new(2322u64)
1210            .script_gas_limit(1)
1211            .coin_input(Default::default(), input_amount)
1212            .change_output(Default::default())
1213            .build()
1214            .transaction()
1215            .clone()
1216            .into();
1217        let chain_id = ConsensusParameters::default().chain_id();
1218        let transaction_id = tx.id(&chain_id);
1219
1220        let mut producer = create_executor(Default::default(), Default::default());
1221
1222        let mut verifier = create_executor(Default::default(), Default::default());
1223
1224        let mut block = Block::default();
1225        *block.transactions_mut() = vec![tx];
1226
1227        let ExecutionResult { mut block, .. } =
1228            producer.produce_and_commit(block.into()).unwrap();
1229
1230        // modify change amount
1231        if let Transaction::Script(script) = &mut block.transactions_mut()[0]
1232            && let Output::Change { amount, .. } = &mut script.outputs_mut()[0]
1233        {
1234            *amount = fake_output_amount
1235        }
1236
1237        // then
1238        let err = verifier.validate_and_commit(&block).unwrap_err();
1239        assert_eq!(
1240            err,
1241            ExecutorError::InvalidTransactionOutcome { transaction_id }
1242        );
1243    }
1244
1245    // corrupt the merkle sum tree commitment from a produced block and verify that the
1246    // validation logic will reject the block
1247    #[test]
1248    fn executor_invalidates_blocks_with_diverging_tx_commitment() {
1249        let mut rng = StdRng::seed_from_u64(2322u64);
1250        let tx: Transaction = TxBuilder::new(2322u64)
1251            .script_gas_limit(1)
1252            .coin_input(Default::default(), 10)
1253            .change_output(Default::default())
1254            .build()
1255            .transaction()
1256            .clone()
1257            .into();
1258
1259        let mut producer = create_executor(Default::default(), Default::default());
1260
1261        let mut verifier = create_executor(Default::default(), Default::default());
1262
1263        let mut block = Block::default();
1264        *block.transactions_mut() = vec![tx];
1265
1266        let ExecutionResult { mut block, .. } =
1267            producer.produce_and_commit(block.into()).unwrap();
1268
1269        // randomize transaction commitment
1270        block.header_mut().set_transaction_root(rng.r#gen());
1271        block.header_mut().recalculate_metadata();
1272
1273        let err = verifier.validate_and_commit(&block).unwrap_err();
1274
1275        assert_eq!(err, ExecutorError::BlockMismatch)
1276    }
1277
1278    // invalidate a block if a tx is missing at least one coin input
1279    #[test]
1280    fn executor_invalidates_missing_coin_input() {
1281        let mut tx: Script = Script::default();
1282        tx.policies_mut().set(PolicyType::MaxFee, Some(0));
1283
1284        let mut executor = create_executor(
1285            Database::default(),
1286            Config {
1287                forbid_fake_coins_default: true,
1288                ..Default::default()
1289            },
1290        );
1291
1292        let block = PartialFuelBlock {
1293            header: Default::default(),
1294            transactions: vec![tx.into()],
1295        };
1296
1297        let ExecutionResult {
1298            skipped_transactions,
1299            ..
1300        } = executor.produce_and_commit(block).unwrap();
1301
1302        let err = &skipped_transactions[0].1;
1303        // assert block failed to validate when transaction didn't contain any coin inputs
1304        assert!(matches!(
1305            err,
1306            &ExecutorError::InvalidTransaction(CheckError::Validity(
1307                ValidityError::NoSpendableInput
1308            ))
1309        ));
1310    }
1311
1312    #[test]
1313    fn skipped_tx_not_changed_spent_status() {
1314        // `tx2` has two inputs: one used by `tx1` and on random. So after the execution of `tx1`,
1315        // the `tx2` become invalid and should be skipped by the block producers. Skipped
1316        // transactions should not affect the state so the second input should be `Unspent`.
1317        // # Dev-note: `TxBuilder::new(2322u64)` is used to create transactions, it produces
1318        // the same first input.
1319        let tx1 = TxBuilder::new(2322u64)
1320            .coin_input(AssetId::default(), 100)
1321            .change_output(AssetId::default())
1322            .build()
1323            .transaction()
1324            .clone();
1325
1326        let tx2 = TxBuilder::new(2322u64)
1327            // The same input as `tx1`
1328            .coin_input(AssetId::default(), 100)
1329            // Additional unique for `tx2` input
1330            .coin_input(AssetId::default(), 100)
1331            .change_output(AssetId::default())
1332            .build()
1333            .transaction()
1334            .clone();
1335
1336        let first_input = tx2.inputs()[0].clone();
1337        let mut first_coin = CompressedCoin::default();
1338        first_coin.set_owner(*first_input.input_owner().unwrap());
1339        first_coin.set_amount(100);
1340        let second_input = tx2.inputs()[1].clone();
1341        let mut second_coin = CompressedCoin::default();
1342        second_coin.set_owner(*second_input.input_owner().unwrap());
1343        second_coin.set_amount(100);
1344        let db = &mut Database::default();
1345        // Insert both inputs
1346        db.storage::<Coins>()
1347            .insert(&first_input.utxo_id().unwrap().clone(), &first_coin)
1348            .unwrap();
1349        db.storage::<Coins>()
1350            .insert(&second_input.utxo_id().unwrap().clone(), &second_coin)
1351            .unwrap();
1352        let mut executor = create_executor(
1353            db.clone(),
1354            Config {
1355                forbid_fake_coins_default: true,
1356                ..Default::default()
1357            },
1358        );
1359
1360        let block = PartialFuelBlock {
1361            header: Default::default(),
1362            transactions: vec![tx1.into(), tx2.clone().into()],
1363        };
1364
1365        // The first input should be `Unspent` before execution.
1366        db.storage::<Coins>()
1367            .get(first_input.utxo_id().unwrap())
1368            .unwrap()
1369            .expect("coin should be unspent");
1370        // The second input should be `Unspent` before execution.
1371        db.storage::<Coins>()
1372            .get(second_input.utxo_id().unwrap())
1373            .unwrap()
1374            .expect("coin should be unspent");
1375
1376        let ExecutionResult {
1377            block,
1378            skipped_transactions,
1379            ..
1380        } = executor.produce_and_commit(block).unwrap();
1381        // `tx2` should be skipped.
1382        assert_eq!(block.transactions().len(), 2 /* coinbase and `tx1` */);
1383        assert_eq!(skipped_transactions.len(), 1);
1384        assert_eq!(skipped_transactions[0].0, tx2.id(&ChainId::default()));
1385
1386        // The first input should be spent by `tx1` after execution.
1387        let coin = db
1388            .storage::<Coins>()
1389            .get(first_input.utxo_id().unwrap())
1390            .unwrap();
1391        // verify coin is pruned from utxo set
1392        assert!(coin.is_none());
1393        // The second input should be `Unspent` after execution.
1394        db.storage::<Coins>()
1395            .get(second_input.utxo_id().unwrap())
1396            .unwrap()
1397            .expect("coin should be unspent");
1398    }
1399
1400    #[test]
1401    fn coin_input_fails_when_mismatches_database() {
1402        const AMOUNT: u64 = 100;
1403
1404        let tx = TxBuilder::new(2322u64)
1405            .coin_input(AssetId::default(), AMOUNT)
1406            .change_output(AssetId::default())
1407            .build()
1408            .transaction()
1409            .clone();
1410
1411        let input = tx.inputs()[0].clone();
1412        let mut coin = CompressedCoin::default();
1413        coin.set_owner(*input.input_owner().unwrap());
1414        coin.set_amount(AMOUNT - 1);
1415        let db = &mut Database::default();
1416
1417        // Inserting a coin with `AMOUNT - 1` should cause a mismatching error during production.
1418        db.storage::<Coins>()
1419            .insert(&input.utxo_id().unwrap().clone(), &coin)
1420            .unwrap();
1421        let mut executor = create_executor(
1422            db.clone(),
1423            Config {
1424                forbid_fake_coins_default: true,
1425                ..Default::default()
1426            },
1427        );
1428
1429        let block = PartialFuelBlock {
1430            header: Default::default(),
1431            transactions: vec![tx.into()],
1432        };
1433
1434        let ExecutionResult {
1435            skipped_transactions,
1436            ..
1437        } = executor.produce_and_commit(block).unwrap();
1438        // `tx` should be skipped.
1439        assert_eq!(skipped_transactions.len(), 1);
1440        let err = &skipped_transactions[0].1;
1441        assert!(matches!(
1442            err,
1443            &ExecutorError::TransactionValidity(TransactionValidityError::CoinMismatch(
1444                _
1445            ))
1446        ));
1447    }
1448
1449    #[test]
1450    fn contract_input_fails_when_doesnt_exist_in_database() {
1451        let contract_id: ContractId = [1; 32].into();
1452        let tx = TxBuilder::new(2322u64)
1453            .contract_input(contract_id)
1454            .coin_input(AssetId::default(), 100)
1455            .change_output(AssetId::default())
1456            .contract_output(&contract_id)
1457            .build()
1458            .transaction()
1459            .clone();
1460
1461        let input = tx.inputs()[1].clone();
1462        let mut coin = CompressedCoin::default();
1463        coin.set_owner(*input.input_owner().unwrap());
1464        coin.set_amount(100);
1465        let db = &mut Database::default();
1466
1467        db.storage::<Coins>()
1468            .insert(&input.utxo_id().unwrap().clone(), &coin)
1469            .unwrap();
1470        let mut executor = create_executor(
1471            db.clone(),
1472            Config {
1473                forbid_fake_coins_default: true,
1474                ..Default::default()
1475            },
1476        );
1477
1478        let block = PartialFuelBlock {
1479            header: Default::default(),
1480            transactions: vec![tx.into()],
1481        };
1482
1483        let ExecutionResult {
1484            skipped_transactions,
1485            ..
1486        } = executor.produce_and_commit(block).unwrap();
1487        // `tx` should be skipped.
1488        assert_eq!(skipped_transactions.len(), 1);
1489        let err = &skipped_transactions[0].1;
1490        assert!(matches!(
1491            err,
1492            &ExecutorError::TransactionValidity(
1493                TransactionValidityError::ContractDoesNotExist(_)
1494            )
1495        ));
1496    }
1497
1498    #[test]
1499    fn transaction_consuming_too_much_gas_are_skipped() {
1500        // Gather the gas consumption of the transaction
1501        let mut executor = create_executor(Default::default(), Default::default());
1502        let block: PartialFuelBlock = PartialFuelBlock {
1503            header: Default::default(),
1504            transactions: vec![
1505                TransactionBuilder::script(vec![], vec![])
1506                    .max_fee_limit(100_000_000)
1507                    .add_fee_input()
1508                    .script_gas_limit(0)
1509                    .tip(123)
1510                    .finalize_as_transaction(),
1511            ],
1512        };
1513
1514        // When
1515        let ExecutionResult { tx_status, .. } =
1516            executor.produce_and_commit(block).unwrap();
1517        let tx_gas_usage = tx_status[0].result.total_gas();
1518
1519        // Given
1520        let mut txs = vec![];
1521        for i in 0..10 {
1522            let tx = TransactionBuilder::script(vec![], vec![])
1523                .max_fee_limit(100_000_000)
1524                .add_fee_input()
1525                .script_gas_limit(0)
1526                .tip(i * 100)
1527                .finalize_as_transaction();
1528            txs.push(tx);
1529        }
1530        let mut config: Config = Default::default();
1531        // Each TX consumes `tx_gas_usage` gas and so set the block gas limit to execute only 9 transactions.
1532        let block_gas_limit = tx_gas_usage * 9;
1533        config
1534            .consensus_parameters
1535            .set_block_gas_limit(block_gas_limit);
1536        let mut executor = create_executor(Default::default(), config);
1537
1538        let block = PartialFuelBlock {
1539            header: Default::default(),
1540            transactions: txs,
1541        };
1542
1543        // When
1544        let ExecutionResult {
1545            skipped_transactions,
1546            ..
1547        } = executor.produce_and_commit(block).unwrap();
1548
1549        // Then
1550        assert_eq!(skipped_transactions.len(), 1);
1551        assert_eq!(
1552            skipped_transactions[0].1,
1553            ExecutorError::GasOverflow(
1554                "Transaction cannot fit in remaining gas limit: (0).".into(),
1555                *tx_gas_usage,
1556                0
1557            )
1558        );
1559    }
1560
1561    #[test]
1562    fn skipped_txs_not_affect_order() {
1563        // `tx1` is invalid because it doesn't have inputs for max fee.
1564        // `tx2` is a `Create` transaction with some code inside.
1565        // `tx3` is a `Script` transaction that depends on `tx2`. It will be skipped
1566        // if `tx2` is not executed before `tx3`.
1567        //
1568        // The test checks that execution for the block with transactions [tx1, tx2, tx3] skips
1569        // transaction `tx1` and produce a block [tx2, tx3] with the expected order.
1570        let tx1 = TransactionBuilder::script(vec![], vec![])
1571            .add_fee_input()
1572            .script_gas_limit(1000000)
1573            .tip(1000000)
1574            .finalize_as_transaction();
1575        let (tx2, tx3) = setup_executable_script();
1576
1577        let mut executor = create_executor(Default::default(), Default::default());
1578
1579        let block = PartialFuelBlock {
1580            header: Default::default(),
1581            transactions: vec![tx1.clone(), tx2.clone().into(), tx3.clone().into()],
1582        };
1583
1584        let ExecutionResult {
1585            block,
1586            skipped_transactions,
1587            ..
1588        } = executor.produce_and_commit(block).unwrap();
1589        assert_eq!(
1590            block.transactions().len(),
1591            3 // coinbase, `tx2` and `tx3`
1592        );
1593        assert_eq!(
1594            block.transactions()[0].id(&ChainId::default()),
1595            tx2.id(&ChainId::default())
1596        );
1597        assert_eq!(
1598            block.transactions()[1].id(&ChainId::default()),
1599            tx3.id(&ChainId::default())
1600        );
1601        // `tx1` should be skipped.
1602        assert_eq!(skipped_transactions.len(), 1);
1603        assert_eq!(&skipped_transactions[0].0, &tx1.id(&ChainId::default()));
1604        let tx2_index_in_the_block =
1605            block.transactions()[1].as_script().unwrap().inputs()[0]
1606                .tx_pointer()
1607                .unwrap()
1608                .tx_index();
1609        assert_eq!(tx2_index_in_the_block, 0);
1610    }
1611
1612    #[test]
1613    fn input_coins_are_marked_as_spent() {
1614        // ensure coins are marked as spent after tx is processed
1615        let tx: Transaction = TxBuilder::new(2322u64)
1616            .coin_input(AssetId::default(), 100)
1617            .change_output(AssetId::default())
1618            .build()
1619            .transaction()
1620            .clone()
1621            .into();
1622
1623        let db = &Database::default();
1624        let mut executor = create_executor(db.clone(), Default::default());
1625
1626        let block = PartialFuelBlock {
1627            header: Default::default(),
1628            transactions: vec![tx],
1629        };
1630
1631        let ExecutionResult { block, .. } = executor.produce_and_commit(block).unwrap();
1632
1633        // assert the tx coin is spent
1634        let coin = db
1635            .storage::<Coins>()
1636            .get(
1637                block.transactions()[0].as_script().unwrap().inputs()[0]
1638                    .utxo_id()
1639                    .unwrap(),
1640            )
1641            .unwrap();
1642        // spent coins should be removed
1643        assert!(coin.is_none());
1644    }
1645
1646    #[test]
1647    fn contracts_balance_and_state_roots_no_modifications_updated() {
1648        // Values in inputs and outputs are random. If the execution of the transaction successful,
1649        // it should actualize them to use a valid the balance and state roots. Because it is not
1650        // changes, the balance the root should be default - `[0; 32]`.
1651        let mut rng = StdRng::seed_from_u64(2322u64);
1652
1653        let (create, contract_id) = create_contract(&[], &mut rng);
1654        let non_modify_state_tx: Transaction = TxBuilder::new(2322)
1655            .script_gas_limit(10000)
1656            .coin_input(AssetId::zeroed(), 10000)
1657            .start_script(vec![op::ret(1)], vec![])
1658            .contract_input(contract_id)
1659            .fee_input()
1660            .contract_output(&contract_id)
1661            .build()
1662            .transaction()
1663            .clone()
1664            .into();
1665
1666        let db = &mut Database::default();
1667        let mut executor = create_executor(
1668            db.clone(),
1669            Config {
1670                forbid_fake_coins_default: false,
1671                ..Default::default()
1672            },
1673        );
1674
1675        let block = PartialFuelBlock {
1676            header: PartialBlockHeader {
1677                consensus: ConsensusHeader {
1678                    height: 1.into(),
1679                    ..Default::default()
1680                },
1681                ..Default::default()
1682            },
1683            transactions: vec![create.into(), non_modify_state_tx],
1684        };
1685
1686        let ExecutionResult {
1687            block, tx_status, ..
1688        } = executor.produce_and_commit(block).unwrap();
1689
1690        // Ensure all txs succeeded.
1691        assert!(
1692            tx_status
1693                .iter()
1694                .all(|s| matches!(s.result, TransactionExecutionResult::Success { .. }))
1695        );
1696
1697        // Assert the balance and state roots are same before and after execution.
1698        let executed_tx = block.transactions()[1].as_script().unwrap();
1699        assert_eq!(
1700            executed_tx.inputs()[0].state_root(),
1701            Some(&Bytes32::zeroed())
1702        );
1703        assert_eq!(
1704            executed_tx.inputs()[0].balance_root(),
1705            Some(&Bytes32::zeroed())
1706        );
1707        assert_eq!(
1708            executed_tx.outputs()[0].state_root(),
1709            Some(&Bytes32::zeroed())
1710        );
1711        assert_eq!(
1712            executed_tx.outputs()[0].balance_root(),
1713            Some(&Bytes32::zeroed())
1714        );
1715    }
1716
1717    #[test]
1718    fn contracts_balance_and_state_roots_updated_no_modifications_on_fail() {
1719        // Values in inputs and outputs are random. If the execution of the transaction fails,
1720        // it still should actualize them to use the balance and state roots before the execution.
1721        let mut rng = StdRng::seed_from_u64(2322u64);
1722
1723        let (create, contract_id) = create_contract(&[], &mut rng);
1724        // The transaction with invalid script.
1725        let non_modify_state_tx: Transaction = TxBuilder::new(2322)
1726            .start_script(vec![op::add(RegId::PC, RegId::PC, RegId::PC)], vec![])
1727            .contract_input(contract_id)
1728            .fee_input()
1729            .contract_output(&contract_id)
1730            .build()
1731            .transaction()
1732            .clone()
1733            .into();
1734
1735        let db = &mut Database::default();
1736        let mut executor = create_executor(
1737            db.clone(),
1738            Config {
1739                forbid_fake_coins_default: false,
1740                ..Default::default()
1741            },
1742        );
1743
1744        let block = PartialFuelBlock {
1745            header: PartialBlockHeader {
1746                consensus: ConsensusHeader {
1747                    height: 1.into(),
1748                    ..Default::default()
1749                },
1750                ..Default::default()
1751            },
1752            transactions: vec![create.into(), non_modify_state_tx],
1753        };
1754
1755        let ExecutionResult {
1756            block, tx_status, ..
1757        } = executor.produce_and_commit(block).unwrap();
1758
1759        // Assert the balance and state roots should be the same before and after execution.
1760        let executed_tx = block.transactions()[1].as_script().unwrap();
1761        assert!(matches!(
1762            tx_status[1].result,
1763            TransactionExecutionResult::Failed { .. }
1764        ));
1765        assert_eq!(
1766            executed_tx.inputs()[0].state_root(),
1767            executed_tx.outputs()[0].state_root()
1768        );
1769        assert_eq!(
1770            executed_tx.inputs()[0].balance_root(),
1771            executed_tx.outputs()[0].balance_root()
1772        );
1773
1774        // Both balance and state roots are empty, and should match hash of empty data.
1775        assert_eq!(
1776            executed_tx.inputs()[0].balance_root(),
1777            Some(&Bytes32::zeroed())
1778        );
1779        assert_eq!(
1780            executed_tx.inputs()[0].state_root(),
1781            Some(&Bytes32::zeroed())
1782        );
1783    }
1784
1785    #[test]
1786    fn contracts_balance_and_state_roots_updated_modifications_updated() {
1787        // Values in inputs and outputs are random. If the execution of the transaction that
1788        // modifies the state and the balance is successful, it should update roots.
1789        let mut rng = StdRng::seed_from_u64(2322u64);
1790
1791        // Create a contract that modifies the state
1792        let (create, contract_id) = create_contract(
1793            // Increment the slot matching the tx id by one
1794            vec![
1795                op::srw(0x10, 0x29, RegId::ZERO),
1796                op::addi(0x10, 0x10, 1),
1797                op::sww(RegId::ZERO, 0x29, 0x10),
1798                op::ret(1),
1799            ]
1800            .into_iter()
1801            .collect::<Vec<u8>>()
1802            .as_slice(),
1803            &mut rng,
1804        );
1805
1806        let transfer_amount = 100 as Word;
1807        let asset_id = AssetId::from([2; 32]);
1808        let (script, _) = script_with_data_offset!(
1809            data_offset,
1810            vec![
1811                // Set register `0x10` to `Call`
1812                op::movi(0x10, data_offset + AssetId::LEN as u32),
1813                // Set register `0x11` with offset to data that contains `asset_id`
1814                op::movi(0x11, data_offset),
1815                // Set register `0x12` with `transfer_amount`
1816                op::movi(0x12, transfer_amount as u32),
1817                op::call(0x10, 0x12, 0x11, RegId::CGAS),
1818                op::ret(RegId::ONE),
1819            ],
1820            TxParameters::DEFAULT.tx_offset()
1821        );
1822
1823        let script_data: Vec<u8> = [
1824            asset_id.as_ref(),
1825            Call::new(contract_id, 0, 0).to_bytes().as_ref(),
1826        ]
1827        .into_iter()
1828        .flatten()
1829        .copied()
1830        .collect();
1831
1832        let modify_balance_and_state_tx = TxBuilder::new(2322)
1833            .script_gas_limit(10000)
1834            .coin_input(AssetId::zeroed(), 10000)
1835            .start_script(script, script_data)
1836            .contract_input(contract_id)
1837            .coin_input(asset_id, transfer_amount)
1838            .fee_input()
1839            .contract_output(&contract_id)
1840            .build()
1841            .transaction()
1842            .clone();
1843        let db = Database::default();
1844
1845        let mut executor = create_executor(
1846            db.clone(),
1847            Config {
1848                forbid_fake_coins_default: false,
1849                ..Default::default()
1850            },
1851        );
1852
1853        let block = PartialFuelBlock {
1854            header: PartialBlockHeader {
1855                consensus: ConsensusHeader {
1856                    height: 1.into(),
1857                    ..Default::default()
1858                },
1859                ..Default::default()
1860            },
1861            transactions: vec![create.into(), modify_balance_and_state_tx.into()],
1862        };
1863
1864        let ExecutionResult {
1865            block, tx_status, ..
1866        } = executor.produce_and_commit(block).unwrap();
1867
1868        assert!(
1869            tx_status
1870                .iter()
1871                .all(|s| matches!(s.result, TransactionExecutionResult::Success { .. }))
1872        );
1873
1874        let executed_tx = block.transactions()[1].as_script().unwrap();
1875        let tx_id = executed_tx.id(&ConsensusParameters::standard().chain_id());
1876
1877        // Check resulting roots
1878
1879        // Input balances: 0 of asset_id [2; 32]
1880        let mut hasher = Sha256::new();
1881        hasher.update(asset_id);
1882        hasher.update([0u8]);
1883        let expected_balance_root: [u8; 32] = hasher.finalize().into();
1884        assert_eq!(
1885            executed_tx.inputs()[0].balance_root(),
1886            Some(&Bytes32::new(expected_balance_root))
1887        );
1888
1889        // Output balances: 100 of asset_id [2; 32]
1890        let mut hasher = Sha256::new();
1891        hasher.update(asset_id);
1892        hasher.update([1u8]);
1893        hasher.update(100u64.to_be_bytes()); // balance
1894        let expected_balance_root: [u8; 32] = hasher.finalize().into();
1895        assert_eq!(
1896            executed_tx.outputs()[0].balance_root(),
1897            Some(&Bytes32::new(expected_balance_root))
1898        );
1899
1900        // Input state: empty slot tx_id
1901        let mut hasher = Sha256::new();
1902        hasher.update(tx_id); // the slot key that is modified
1903        hasher.update([0u8]); // the slot did not contain any value
1904        let expected_state_root: [u8; 32] = hasher.finalize().into();
1905        assert_eq!(
1906            executed_tx.inputs()[0].state_root(),
1907            Some(&Bytes32::new(expected_state_root))
1908        );
1909
1910        // Output state: slot tx_id with value 1
1911        let mut hasher = Sha256::new();
1912        hasher.update(tx_id); // the slot key that is modified
1913        hasher.update([1u8]); // the slot contains a value
1914        hasher.update(32u64.to_be_bytes()); // slot size is 32 bytes
1915        hasher.update({
1916            let mut value = [0u8; 32];
1917            value[..8].copy_from_slice(&1u64.to_be_bytes()); // the value is 1
1918            value
1919        }); // The value in the slot is 1
1920        let expected_state_root: [u8; 32] = hasher.finalize().into();
1921        assert_eq!(
1922            executed_tx.outputs()[0].state_root(),
1923            Some(&Bytes32::new(expected_state_root))
1924        );
1925    }
1926
1927    #[test]
1928    fn contracts_balance_and_state_roots_are_zero_after_reverted_execution() {
1929        // Values in inputs and outputs are random. If the execution of the transaction that
1930        // modifies the state and the balance is successful, it should update roots.
1931        let mut rng = StdRng::seed_from_u64(2322u64);
1932
1933        // Create a contract that modifies the state
1934        let (create, contract_id) = create_contract(
1935            // Increment the slot matching the tx id by one
1936            vec![
1937                op::srw(0x10, 0x29, RegId::ZERO),
1938                op::addi(0x10, 0x10, 1),
1939                op::sww(RegId::ZERO, 0x29, 0x10),
1940                op::ret(1),
1941            ]
1942            .into_iter()
1943            .collect::<Vec<u8>>()
1944            .as_slice(),
1945            &mut rng,
1946        );
1947
1948        let transfer_amount = 100 as Word;
1949        let asset_id = AssetId::from([2; 32]);
1950        let (script, _) = script_with_data_offset!(
1951            data_offset,
1952            vec![
1953                // Set register `0x10` to `Call`
1954                op::movi(0x10, data_offset + AssetId::LEN as u32),
1955                // Set register `0x11` with offset to data that contains `asset_id`
1956                op::movi(0x11, data_offset),
1957                // Set register `0x12` with `transfer_amount`
1958                op::movi(0x12, transfer_amount as u32),
1959                op::call(0x10, 0x12, 0x11, RegId::CGAS),
1960                // Revert the execution
1961                op::rvrt(RegId::ONE),
1962            ],
1963            TxParameters::DEFAULT.tx_offset()
1964        );
1965
1966        let script_data: Vec<u8> = [
1967            asset_id.as_ref(),
1968            Call::new(contract_id, 0, 0).to_bytes().as_ref(),
1969        ]
1970        .into_iter()
1971        .flatten()
1972        .copied()
1973        .collect();
1974
1975        let modify_balance_and_state_tx = TxBuilder::new(2322)
1976            .script_gas_limit(10000)
1977            .coin_input(AssetId::zeroed(), 10000)
1978            .start_script(script, script_data)
1979            .contract_input(contract_id)
1980            .coin_input(asset_id, transfer_amount)
1981            .fee_input()
1982            .contract_output(&contract_id)
1983            .build()
1984            .transaction()
1985            .clone();
1986        let db = Database::default();
1987
1988        let mut executor = create_executor(
1989            db.clone(),
1990            Config {
1991                forbid_fake_coins_default: false,
1992                ..Default::default()
1993            },
1994        );
1995
1996        let block = PartialFuelBlock {
1997            header: PartialBlockHeader {
1998                consensus: ConsensusHeader {
1999                    height: 1.into(),
2000                    ..Default::default()
2001                },
2002                ..Default::default()
2003            },
2004            transactions: vec![create.into(), modify_balance_and_state_tx.into()],
2005        };
2006
2007        let ExecutionResult {
2008            block, tx_status, ..
2009        } = executor.produce_and_commit(block).unwrap();
2010
2011        assert!(matches!(
2012            tx_status[1].result,
2013            TransactionExecutionResult::Failed { .. }
2014        ));
2015
2016        let executed_tx = block.transactions()[1].as_script().unwrap();
2017        let tx_id = executed_tx.id(&ConsensusParameters::standard().chain_id());
2018
2019        // Check resulting roots
2020
2021        // Input balances: 0 of asset_id [2; 32]
2022        let mut hasher = Sha256::new();
2023        hasher.update(asset_id);
2024        hasher.update([0u8]);
2025        let expected_balance_root: [u8; 32] = hasher.finalize().into();
2026        assert_eq!(
2027            executed_tx.inputs()[0].balance_root(),
2028            Some(&Bytes32::new(expected_balance_root))
2029        );
2030
2031        // Output balances: 100 of asset_id [2; 32]
2032        assert_eq!(
2033            executed_tx.outputs()[0].balance_root(),
2034            Some(&Bytes32::zeroed())
2035        );
2036
2037        // Input state: empty slot tx_id
2038        let mut hasher = Sha256::new();
2039        hasher.update(tx_id); // the slot key that is modified
2040        hasher.update([0u8]); // the slot did not contain any value
2041        let expected_state_root: [u8; 32] = hasher.finalize().into();
2042        assert_eq!(
2043            executed_tx.inputs()[0].state_root(),
2044            Some(&Bytes32::new(expected_state_root))
2045        );
2046
2047        // Output state: slot tx_id with value 1
2048        assert_eq!(
2049            executed_tx.outputs()[0].state_root(),
2050            Some(&Bytes32::zeroed())
2051        );
2052    }
2053
2054    #[test]
2055    fn contracts_balance_and_state_roots_updated_correctly_with_multiple_modifications() {
2056        // Values in inputs and outputs are random. If the execution of the transaction that
2057        // modifies the state and the balance is successful, it should update roots.
2058        let mut rng = StdRng::seed_from_u64(2322u64);
2059
2060        // Create a contract that modifies the state
2061        let (create, contract_id) = create_contract(
2062            // Increment the slot matching the tx id by one
2063            vec![
2064                op::srw(0x10, 0x29, RegId::ZERO),
2065                op::addi(0x10, 0x10, 1),
2066                op::sww(RegId::ZERO, 0x29, 0x10),
2067                op::ret(1),
2068            ]
2069            .into_iter()
2070            .collect::<Vec<u8>>()
2071            .as_slice(),
2072            &mut rng,
2073        );
2074
2075        let transfer_amount = 100 as Word;
2076        let asset_id = AssetId::from([2; 32]);
2077        let (script, _) = script_with_data_offset!(
2078            data_offset,
2079            vec![
2080                // Set register `0x10` to `Call`
2081                op::movi(0x10, data_offset + AssetId::LEN as u32),
2082                // Set register `0x11` with offset to data that contains `asset_id`
2083                op::movi(0x11, data_offset),
2084                // Set register `0x12` with `transfer_amount`
2085                op::movi(0x12, transfer_amount as u32),
2086                op::call(0x10, 0x12, 0x11, RegId::CGAS),
2087                // call again for the second increment, but don't transfer any tokens
2088                op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS),
2089                op::ret(RegId::ONE),
2090            ],
2091            TxParameters::DEFAULT.tx_offset()
2092        );
2093
2094        let script_data: Vec<u8> = [
2095            asset_id.as_ref(),
2096            Call::new(contract_id, 0, 0).to_bytes().as_ref(),
2097        ]
2098        .into_iter()
2099        .flatten()
2100        .copied()
2101        .collect();
2102
2103        let modify_balance_and_state_tx = TxBuilder::new(2322)
2104            .script_gas_limit(10000)
2105            .coin_input(AssetId::zeroed(), 10000)
2106            .start_script(script, script_data)
2107            .contract_input(contract_id)
2108            .coin_input(asset_id, transfer_amount)
2109            .fee_input()
2110            .contract_output(&contract_id)
2111            .build()
2112            .transaction()
2113            .clone();
2114
2115        let db = Database::default();
2116        let mut executor = create_executor(
2117            db.clone(),
2118            Config {
2119                forbid_fake_coins_default: false,
2120                ..Default::default()
2121            },
2122        );
2123
2124        let block = PartialFuelBlock {
2125            header: PartialBlockHeader {
2126                consensus: ConsensusHeader {
2127                    height: 1.into(),
2128                    ..Default::default()
2129                },
2130                ..Default::default()
2131            },
2132            transactions: vec![create.into(), modify_balance_and_state_tx.into()],
2133        };
2134
2135        let ExecutionResult {
2136            block, tx_status, ..
2137        } = executor.produce_and_commit(block).unwrap();
2138        assert!(
2139            tx_status
2140                .iter()
2141                .all(|s| matches!(s.result, TransactionExecutionResult::Success { .. }))
2142        );
2143
2144        let executed_tx = block.transactions()[1].as_script().unwrap();
2145        let tx_id = executed_tx.id(&ConsensusParameters::standard().chain_id());
2146
2147        // Check resulting roots
2148
2149        // Input balances: 0 of asset_id [2; 32]
2150        let mut hasher = Sha256::new();
2151        hasher.update(asset_id);
2152        hasher.update([0u8]);
2153        let expected_balance_root: [u8; 32] = hasher.finalize().into();
2154        assert_eq!(
2155            executed_tx.inputs()[0].balance_root(),
2156            Some(&Bytes32::new(expected_balance_root))
2157        );
2158
2159        // Output balances: 100 of asset_id [2; 32]
2160        let mut hasher = Sha256::new();
2161        hasher.update(asset_id);
2162        hasher.update([1u8]);
2163        hasher.update(100u64.to_be_bytes()); // balance
2164        let expected_balance_root: [u8; 32] = hasher.finalize().into();
2165        assert_eq!(
2166            executed_tx.outputs()[0].balance_root(),
2167            Some(&Bytes32::new(expected_balance_root))
2168        );
2169
2170        // Input state: empty slot tx_id
2171        let mut hasher = Sha256::new();
2172        hasher.update(tx_id); // the slot key that is modified
2173        hasher.update([0u8]); // the slot did not contain any value
2174        let expected_state_root: [u8; 32] = hasher.finalize().into();
2175        assert_eq!(
2176            executed_tx.inputs()[0].state_root(),
2177            Some(&Bytes32::new(expected_state_root))
2178        );
2179
2180        // Output state: slot tx_id with value 2
2181        let mut hasher = Sha256::new();
2182        hasher.update(tx_id); // the slot key that is modified
2183        hasher.update([1u8]); // the slot has a value
2184        hasher.update(32u64.to_be_bytes()); // slot size is 32 bytes
2185        hasher.update({
2186            let mut value = [0u8; 32];
2187            value[..8].copy_from_slice(&2u64.to_be_bytes()); // the value is 2
2188            value
2189        }); // The value in the slot is 1
2190        let expected_state_root: [u8; 32] = hasher.finalize().into();
2191        assert_eq!(
2192            executed_tx.outputs()[0].state_root(),
2193            Some(&Bytes32::new(expected_state_root))
2194        );
2195    }
2196
2197    #[test]
2198    fn contracts_balance_and_state_roots_updated_correctly_after_modifying_multiple_slots()
2199     {
2200        // Values in inputs and outputs are random. If the execution of the transaction that
2201        // modifies the state and the balance is successful, it should update roots.
2202        let mut rng = StdRng::seed_from_u64(2322u64);
2203
2204        // Create a contract that modifies the state
2205        let (create, contract_id) = create_contract(
2206            // Set first four slots to contain their slot keysz
2207            vec![
2208                // Allocate space for the ids
2209                op::movi(0x10, 32),
2210                op::aloc(0x10),
2211                // Store the values
2212                op::movi(0x10, 0),
2213                op::sww(RegId::HP, 0x29, 0x10),
2214                op::addi(0x10, 0x10, 1),
2215                op::sw(RegId::HP, 0x10, 0),
2216                op::sww(RegId::HP, 0x29, 0x10),
2217                op::addi(0x10, 0x10, 1),
2218                op::sw(RegId::HP, 0x10, 0),
2219                op::sww(RegId::HP, 0x29, 0x10),
2220                op::addi(0x10, 0x10, 1),
2221                op::sw(RegId::HP, 0x10, 0),
2222                op::sww(RegId::HP, 0x29, 0x10),
2223                // Done
2224                op::ret(1),
2225            ]
2226            .into_iter()
2227            .collect::<Vec<u8>>()
2228            .as_slice(),
2229            &mut rng,
2230        );
2231
2232        let transfer_amount = 100 as Word;
2233        let asset_id = AssetId::from([2; 32]);
2234        let (script, _) = script_with_data_offset!(
2235            data_offset,
2236            vec![
2237                // Set register `0x10` to `Call`
2238                op::movi(0x10, data_offset + AssetId::LEN as u32),
2239                op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS),
2240                op::ret(RegId::ONE),
2241            ],
2242            TxParameters::DEFAULT.tx_offset()
2243        );
2244
2245        let script_data: Vec<u8> = [
2246            asset_id.as_ref(),
2247            Call::new(contract_id, 0, 0).to_bytes().as_ref(),
2248        ]
2249        .into_iter()
2250        .flatten()
2251        .copied()
2252        .collect();
2253
2254        let mut builder = TxBuilder::new(2322);
2255
2256        let tx_1 = builder
2257            .script_gas_limit(10000)
2258            .coin_input(AssetId::zeroed(), 10000)
2259            .start_script(script.clone(), script_data.clone())
2260            .contract_input(contract_id)
2261            .coin_input(asset_id, transfer_amount)
2262            .fee_input()
2263            .contract_output(&contract_id)
2264            .build()
2265            .transaction()
2266            .clone();
2267
2268        let tx_2 = builder
2269            .script_gas_limit(10000)
2270            .coin_input(AssetId::zeroed(), 10000)
2271            .start_script(script, script_data)
2272            .contract_input(contract_id)
2273            .coin_input(asset_id, transfer_amount)
2274            .fee_input()
2275            .contract_output(&contract_id)
2276            .build()
2277            .transaction()
2278            .clone();
2279
2280        let db = Database::default();
2281        let mut executor = create_executor(
2282            db.clone(),
2283            Config {
2284                forbid_fake_coins_default: false,
2285                ..Default::default()
2286            },
2287        );
2288
2289        let block = PartialFuelBlock {
2290            header: PartialBlockHeader {
2291                consensus: ConsensusHeader {
2292                    height: 1.into(),
2293                    ..Default::default()
2294                },
2295                ..Default::default()
2296            },
2297            transactions: vec![create.into(), tx_1.into(), tx_2.into()],
2298        };
2299
2300        let ExecutionResult {
2301            block, tx_status, ..
2302        } = executor.produce_and_commit(block).unwrap();
2303        assert!(matches!(
2304            tx_status[3].result,
2305            TransactionExecutionResult::Success { .. }
2306        ));
2307
2308        let executed_tx_1 = block.transactions()[1].as_script().unwrap();
2309        let executed_tx_2 = block.transactions()[2].as_script().unwrap();
2310
2311        // Check resulting roots
2312
2313        // Input/Output balances for both txs: None
2314        assert_eq!(
2315            executed_tx_1.inputs()[0].balance_root(),
2316            Some(&Bytes32::zeroed())
2317        );
2318        assert_eq!(
2319            executed_tx_1.outputs()[0].balance_root(),
2320            Some(&Bytes32::zeroed())
2321        );
2322        assert_eq!(
2323            executed_tx_2.inputs()[0].balance_root(),
2324            Some(&Bytes32::zeroed())
2325        );
2326        assert_eq!(
2327            executed_tx_2.outputs()[0].balance_root(),
2328            Some(&Bytes32::zeroed())
2329        );
2330
2331        // Input state for tx 1: empty slots
2332        let mut hasher = Sha256::new();
2333        for i in 0..4u64 {
2334            let mut slot_id = [0u8; 32];
2335            slot_id[..8].copy_from_slice(&i.to_be_bytes());
2336            hasher.update(slot_id); // the slot key that is modified
2337            hasher.update([0u8]); // the slot did not contain any value
2338        }
2339        let expected_state_root: [u8; 32] = hasher.finalize().into();
2340        assert_eq!(
2341            executed_tx_1.inputs()[0].state_root(),
2342            Some(&Bytes32::new(expected_state_root))
2343        );
2344
2345        // Output state for tx 1 and input/output state for tx 1: slots with values 0, 1, 2, 3
2346        let mut hasher = Sha256::new();
2347        for i in 0..4u64 {
2348            let mut slot_id = [0u8; 32];
2349            slot_id[..8].copy_from_slice(&i.to_be_bytes());
2350            hasher.update(slot_id); // the slot key that is modified
2351            hasher.update([1u8]); // the slot contains a value
2352            hasher.update(32u64.to_be_bytes()); // slot size is 32 bytes
2353            hasher.update(slot_id); // slot value (matches the id)
2354        }
2355        let expected_state_root: [u8; 32] = hasher.finalize().into();
2356        assert_eq!(
2357            executed_tx_1.outputs()[0].state_root(),
2358            Some(&Bytes32::new(expected_state_root))
2359        );
2360        assert_eq!(
2361            executed_tx_2.inputs()[0].state_root(),
2362            Some(&Bytes32::new(expected_state_root))
2363        );
2364        assert_eq!(
2365            executed_tx_2.outputs()[0].state_root(),
2366            Some(&Bytes32::new(expected_state_root))
2367        );
2368    }
2369
2370    /// Creates two different contracts that modify the state and calls them both from a single
2371    /// transaction. Then creates another transaction that calls one of the contracts again.
2372    /// Ensures the balance and state roots are updated correctly after each call, and that they
2373    /// are properly independent between the two contracts.
2374    #[test]
2375    fn contracts_balance_and_state_roots_updated_correctly_after_calling_multiple_contracts()
2376     {
2377        let mut rng = StdRng::seed_from_u64(2322u64);
2378
2379        // Increment the slot matching the tx id by one
2380        let contract_code = vec![
2381            op::srw(0x10, 0x29, RegId::ZERO),
2382            op::addi(0x10, 0x10, 1),
2383            op::sww(RegId::ZERO, 0x29, 0x10),
2384            op::ret(1),
2385        ]
2386        .into_iter()
2387        .collect::<Vec<u8>>();
2388
2389        // Create a two different contracts that modify the state
2390        let (create1, contract_id1) = create_contract(&contract_code, &mut rng);
2391        let (create2, contract_id2) = create_contract(&contract_code, &mut rng);
2392
2393        let transfer_amount = 100 as Word;
2394        let asset_id = AssetId::from([2; 32]);
2395
2396        let mut builder = TxBuilder::new(2322);
2397
2398        let (script1, _) = script_with_data_offset!(
2399            data_offset,
2400            vec![
2401                // Set register `0x11` with offset to data that contains `asset_id`
2402                op::movi(0x11, data_offset),
2403                // Set register `0x12` with `transfer_amount`
2404                op::movi(0x12, transfer_amount as u32),
2405                // Call first contract
2406                op::movi(0x10, data_offset + AssetId::LEN as u32),
2407                op::call(0x10, 0x12, 0x11, RegId::CGAS),
2408                // Call second contract
2409                op::movi(0x10, data_offset + AssetId::LEN as u32 + Call::LEN as u32),
2410                op::call(0x10, 0x12, 0x11, RegId::CGAS),
2411                op::ret(RegId::ONE),
2412            ],
2413            TxParameters::DEFAULT.tx_offset()
2414        );
2415
2416        let script_data1: Vec<u8> = [
2417            asset_id.as_ref(),
2418            Call::new(contract_id1, 0, 0).to_bytes().as_ref(),
2419            Call::new(contract_id2, 0, 0).to_bytes().as_ref(),
2420        ]
2421        .into_iter()
2422        .flatten()
2423        .copied()
2424        .collect();
2425
2426        let tx1 = builder
2427            .script_gas_limit(10000)
2428            .coin_input(AssetId::zeroed(), 10000)
2429            .start_script(script1, script_data1)
2430            .contract_input(contract_id1)
2431            .contract_input(contract_id2)
2432            .coin_input(asset_id, transfer_amount * 2)
2433            .fee_input()
2434            .contract_output(&contract_id1)
2435            .contract_output(&contract_id2)
2436            .build()
2437            .transaction()
2438            .clone();
2439
2440        let (script2, _) = script_with_data_offset!(
2441            data_offset,
2442            vec![
2443                // Set register `0x11` with offset to data that contains `asset_id`
2444                op::movi(0x11, data_offset),
2445                // Set register `0x12` with `transfer_amount`
2446                op::movi(0x12, transfer_amount as u32),
2447                // Call first contract
2448                op::movi(0x10, data_offset + AssetId::LEN as u32),
2449                op::call(0x10, 0x12, 0x11, RegId::CGAS),
2450                op::ret(RegId::ONE),
2451            ],
2452            TxParameters::DEFAULT.tx_offset()
2453        );
2454
2455        let script_data2: Vec<u8> = [
2456            asset_id.as_ref(),
2457            Call::new(contract_id1, 0, 0).to_bytes().as_ref(),
2458        ]
2459        .into_iter()
2460        .flatten()
2461        .copied()
2462        .collect();
2463
2464        let tx2 = builder
2465            .script_gas_limit(10000)
2466            .coin_input(AssetId::zeroed(), 10000)
2467            .start_script(script2, script_data2)
2468            .contract_input(contract_id1)
2469            .coin_input(asset_id, transfer_amount)
2470            .fee_input()
2471            .contract_output(&contract_id1)
2472            .build()
2473            .transaction()
2474            .clone();
2475
2476        let db = Database::default();
2477        let mut executor = create_executor(
2478            db.clone(),
2479            Config {
2480                forbid_fake_coins_default: false,
2481                ..Default::default()
2482            },
2483        );
2484
2485        let block = PartialFuelBlock {
2486            header: PartialBlockHeader {
2487                consensus: ConsensusHeader {
2488                    height: 1.into(),
2489                    ..Default::default()
2490                },
2491                ..Default::default()
2492            },
2493            transactions: vec![create1.into(), create2.into(), tx1.into(), tx2.into()],
2494        };
2495
2496        let ExecutionResult {
2497            block, tx_status, ..
2498        } = executor.produce_and_commit(block).unwrap();
2499        assert!(
2500            tx_status
2501                .iter()
2502                .all(|s| matches!(s.result, TransactionExecutionResult::Success { .. }))
2503        );
2504
2505        // Check resulting roots
2506
2507        // Tx 1
2508        let executed_tx = block.transactions()[2].as_script().unwrap();
2509        let tx_id1 = executed_tx.id(&ConsensusParameters::standard().chain_id());
2510
2511        let mut contract_ids = [contract_id1, contract_id2];
2512        contract_ids.sort();
2513
2514        // Input balances: 0 of asset_id [2; 32] for both contracts
2515        let mut hasher = Sha256::new();
2516        hasher.update(asset_id);
2517        hasher.update([0u8]);
2518        let expected_balance_root: [u8; 32] = hasher.finalize().into();
2519        assert_eq!(
2520            executed_tx.inputs()[0].balance_root(),
2521            Some(&Bytes32::new(expected_balance_root))
2522        );
2523        assert_eq!(
2524            executed_tx.inputs()[1].balance_root(),
2525            Some(&Bytes32::new(expected_balance_root))
2526        );
2527
2528        // Output balances: 100 of asset_id [2; 32] for both contracts
2529        let mut hasher = Sha256::new();
2530        hasher.update(asset_id);
2531        hasher.update([1u8]);
2532        hasher.update(100u64.to_be_bytes()); // balance
2533        let expected_balance_root: [u8; 32] = hasher.finalize().into();
2534        assert_eq!(
2535            executed_tx.outputs()[0].balance_root(),
2536            Some(&Bytes32::new(expected_balance_root))
2537        );
2538        assert_eq!(
2539            executed_tx.outputs()[1].balance_root(),
2540            Some(&Bytes32::new(expected_balance_root))
2541        );
2542
2543        // Input state: empty slots for both contracts
2544        let mut hasher = Sha256::new();
2545        hasher.update(tx_id1); // the slot key matches tx_id
2546        hasher.update([0u8]); // the slot did not contain any value
2547        let expected_state_root: [u8; 32] = hasher.finalize().into();
2548        assert_eq!(
2549            executed_tx.inputs()[0].state_root(),
2550            Some(&Bytes32::new(expected_state_root))
2551        );
2552        assert_eq!(
2553            executed_tx.inputs()[1].state_root(),
2554            Some(&Bytes32::new(expected_state_root))
2555        );
2556
2557        // Output state: the slot tx_id with value 1 for both contracts
2558        let mut hasher = Sha256::new();
2559        hasher.update(tx_id1); // the slot key matches tx_id
2560        hasher.update([1u8]); // the slot contains a value
2561        hasher.update(32u64.to_be_bytes()); // slot size is 32 bytes
2562        hasher.update({
2563            let mut value = [0u8; 32];
2564            value[..8].copy_from_slice(&1u64.to_be_bytes()); // the value is 1
2565            value
2566        });
2567        let expected_state_root: [u8; 32] = hasher.finalize().into();
2568        assert_eq!(
2569            executed_tx.outputs()[0].state_root(),
2570            Some(&Bytes32::new(expected_state_root))
2571        );
2572        assert_eq!(
2573            executed_tx.outputs()[1].state_root(),
2574            Some(&Bytes32::new(expected_state_root))
2575        );
2576
2577        // Tx 2
2578        let executed_tx = block.transactions()[3].as_script().unwrap();
2579        let tx_id2 = executed_tx.id(&ConsensusParameters::standard().chain_id());
2580
2581        let mut tx_ids = [tx_id1, tx_id2];
2582        tx_ids.sort();
2583
2584        // Input balance: 100 of asset_id [2; 32]
2585        let mut hasher = Sha256::new();
2586        hasher.update(asset_id);
2587        hasher.update([1u8]);
2588        hasher.update(100u64.to_be_bytes()); // balance
2589        let expected_balance_root: [u8; 32] = hasher.finalize().into();
2590        assert_eq!(
2591            executed_tx.inputs()[0].balance_root(),
2592            Some(&Bytes32::new(expected_balance_root))
2593        );
2594
2595        // Output balance: 200 of asset_id [2; 32]
2596        let mut hasher = Sha256::new();
2597        hasher.update(asset_id);
2598        hasher.update([1u8]);
2599        hasher.update(200u64.to_be_bytes()); // balance
2600        let expected_balance_root: [u8; 32] = hasher.finalize().into();
2601        assert_eq!(
2602            executed_tx.outputs()[0].balance_root(),
2603            Some(&Bytes32::new(expected_balance_root))
2604        );
2605
2606        // Input state: one empty slot (from tx 2), the slot from tx 1 is not accessed
2607        let mut hasher = Sha256::new();
2608        hasher.update(tx_id2); // the slot key matches tx_id
2609        hasher.update([0u8]); // the slot contains no value
2610        let expected_state_root: [u8; 32] = hasher.finalize().into();
2611        assert_eq!(
2612            executed_tx.inputs()[0].state_root(),
2613            Some(&Bytes32::new(expected_state_root))
2614        );
2615
2616        // Input state: one slot with value 1 (from tx 2), the slot from tx 1 is not accessed
2617        let mut hasher = Sha256::new();
2618        hasher.update(tx_id2); // the slot key matches tx_id
2619        hasher.update([1u8]); // the slot contains a value
2620        hasher.update(32u64.to_be_bytes()); // slot size is 32 bytes
2621        hasher.update({
2622            let mut value = [0u8; 32];
2623            value[..8].copy_from_slice(&1u64.to_be_bytes()); // the value is 1
2624            value
2625        });
2626        let expected_state_root: [u8; 32] = hasher.finalize().into();
2627        assert_eq!(
2628            executed_tx.outputs()[0].state_root(),
2629            Some(&Bytes32::new(expected_state_root))
2630        );
2631    }
2632
2633    #[test]
2634    fn foreign_transfer_should_not_affect_balance_root() {
2635        // The foreign transfer of tokens should not affect the balance root of the transaction.
2636        let mut rng = StdRng::seed_from_u64(2322u64);
2637
2638        let (create, contract_id) = create_contract(&[], &mut rng);
2639
2640        let transfer_amount = 100 as Word;
2641        let asset_id = AssetId::from([2; 32]);
2642        let mut foreign_transfer = TxBuilder::new(2322)
2643            .script_gas_limit(10000)
2644            .coin_input(AssetId::zeroed(), 10000)
2645            .start_script(vec![op::ret(1)], vec![])
2646            .coin_input(asset_id, transfer_amount)
2647            .coin_output(asset_id, transfer_amount)
2648            .build()
2649            .transaction()
2650            .clone();
2651        if let Some(Output::Coin { to, .. }) = foreign_transfer
2652            .as_script_mut()
2653            .unwrap()
2654            .outputs_mut()
2655            .last_mut()
2656        {
2657            *to = Address::try_from(contract_id.as_ref()).unwrap();
2658        } else {
2659            panic!("Last outputs should be a coin for the contract");
2660        }
2661        let db = &mut Database::default();
2662
2663        let mut executor = create_executor(db.clone(), Default::default());
2664
2665        let block = PartialFuelBlock {
2666            header: PartialBlockHeader {
2667                consensus: ConsensusHeader {
2668                    height: 1.into(),
2669                    ..Default::default()
2670                },
2671                ..Default::default()
2672            },
2673            transactions: vec![create.into(), foreign_transfer.into()],
2674        };
2675
2676        let _ = executor.produce_and_commit(block).unwrap();
2677
2678        // Assert the balance root should not be affected.
2679        assert_eq!(
2680            ContractRef::new(db, contract_id).balance_root().unwrap(),
2681            Bytes32::zeroed()
2682        );
2683    }
2684
2685    #[test]
2686    fn input_coins_are_marked_as_spent_with_utxo_validation_enabled() {
2687        // ensure coins are marked as spent after tx is processed
2688        let mut rng = StdRng::seed_from_u64(2322u64);
2689        let starting_block = BlockHeight::from(5);
2690        let starting_block_tx_idx = Default::default();
2691
2692        let tx = TransactionBuilder::script(
2693            vec![op::ret(RegId::ONE)].into_iter().collect(),
2694            vec![],
2695        )
2696        .add_unsigned_coin_input(
2697            SecretKey::random(&mut rng),
2698            rng.r#gen(),
2699            100,
2700            Default::default(),
2701            Default::default(),
2702        )
2703        .add_output(Output::Change {
2704            to: Default::default(),
2705            amount: 0,
2706            asset_id: Default::default(),
2707        })
2708        .finalize();
2709        let db = &mut Database::default();
2710
2711        // insert coin into state
2712        if let Input::CoinSigned(CoinSigned {
2713            utxo_id,
2714            owner,
2715            amount,
2716            asset_id,
2717            ..
2718        }) = tx.inputs()[0]
2719        {
2720            let mut coin = CompressedCoin::default();
2721            coin.set_owner(owner);
2722            coin.set_amount(amount);
2723            coin.set_asset_id(asset_id);
2724            coin.set_tx_pointer(TxPointer::new(starting_block, starting_block_tx_idx));
2725            db.storage::<Coins>().insert(&utxo_id, &coin).unwrap();
2726        }
2727
2728        let mut executor = create_executor(
2729            db.clone(),
2730            Config {
2731                forbid_fake_coins_default: true,
2732                ..Default::default()
2733            },
2734        );
2735
2736        let block = PartialFuelBlock {
2737            header: PartialBlockHeader {
2738                consensus: ConsensusHeader {
2739                    height: 6.into(),
2740                    ..Default::default()
2741                },
2742                ..Default::default()
2743            },
2744            transactions: vec![tx.into()],
2745        };
2746
2747        let ExecutionResult { block, events, .. } =
2748            executor.produce_and_commit(block).unwrap();
2749
2750        // assert the tx coin is spent
2751        let utxo_id = block.transactions()[0].as_script().unwrap().inputs()[0]
2752            .utxo_id()
2753            .unwrap();
2754        let coin = db.storage::<Coins>().get(utxo_id).unwrap();
2755        assert!(coin.is_none());
2756        assert_eq!(events.len(), 2);
2757        assert!(
2758            matches!(events[0], ExecutorEvent::CoinConsumed(spent_coin) if &spent_coin.utxo_id == utxo_id)
2759        );
2760        assert!(matches!(events[1], ExecutorEvent::CoinCreated(_)));
2761    }
2762
2763    #[test]
2764    fn validation_succeeds_when_input_contract_utxo_id_uses_expected_value() {
2765        let mut rng = StdRng::seed_from_u64(2322);
2766        // create a contract in block 1
2767        // verify a block 2 with tx containing contract id from block 1, using the correct contract utxo_id from block 1.
2768        let (tx, contract_id) = create_contract(&[], &mut rng);
2769        let first_block = PartialFuelBlock {
2770            header: Default::default(),
2771            transactions: vec![tx.into()],
2772        };
2773
2774        let tx2: Transaction = TxBuilder::new(2322)
2775            .start_script(vec![op::ret(1)], vec![])
2776            .contract_input(contract_id)
2777            .fee_input()
2778            .contract_output(&contract_id)
2779            .build()
2780            .transaction()
2781            .clone()
2782            .into();
2783
2784        let second_block = PartialFuelBlock {
2785            header: PartialBlockHeader {
2786                consensus: ConsensusHeader {
2787                    height: 2.into(),
2788                    ..Default::default()
2789                },
2790                ..Default::default()
2791            },
2792            transactions: vec![tx2],
2793        };
2794
2795        let db = Database::default();
2796
2797        let mut setup = create_executor(db.clone(), Default::default());
2798
2799        let ExecutionResult {
2800            skipped_transactions,
2801            ..
2802        } = setup.produce_and_commit(first_block).unwrap();
2803        assert!(skipped_transactions.is_empty());
2804
2805        let producer = create_executor(db.clone(), Default::default());
2806        let ExecutionResult {
2807            block: second_block,
2808            skipped_transactions,
2809            ..
2810        } = producer
2811            .produce_without_commit(second_block)
2812            .unwrap()
2813            .into_result();
2814        assert!(skipped_transactions.is_empty());
2815
2816        let verifier = create_executor(db, Default::default());
2817        let verify_result = verifier.validate(&second_block);
2818        assert!(verify_result.is_ok());
2819    }
2820
2821    // verify that a contract input must exist for a transaction
2822    #[test]
2823    fn invalidates_if_input_contract_utxo_id_is_divergent() {
2824        let mut rng = StdRng::seed_from_u64(2322);
2825
2826        // create a contract in block 1
2827        // verify a block 2 containing contract id from block 1, with wrong input contract utxo_id
2828        let (tx, contract_id) = create_contract(&[], &mut rng);
2829        let tx2: Transaction = TxBuilder::new(2322)
2830            .start_script(vec![op::addi(0x10, RegId::ZERO, 0), op::ret(1)], vec![])
2831            .contract_input(contract_id)
2832            .fee_input()
2833            .contract_output(&contract_id)
2834            .build()
2835            .transaction()
2836            .clone()
2837            .into();
2838
2839        let first_block = PartialFuelBlock {
2840            header: Default::default(),
2841            transactions: vec![tx.into(), tx2],
2842        };
2843
2844        let tx3: Transaction = TxBuilder::new(2322)
2845            .start_script(vec![op::addi(0x10, RegId::ZERO, 1), op::ret(1)], vec![])
2846            .contract_input(contract_id)
2847            .fee_input()
2848            .contract_output(&contract_id)
2849            .build()
2850            .transaction()
2851            .clone()
2852            .into();
2853        let tx_id = tx3.id(&ChainId::default());
2854
2855        let second_block = PartialFuelBlock {
2856            header: PartialBlockHeader {
2857                consensus: ConsensusHeader {
2858                    height: 2.into(),
2859                    ..Default::default()
2860                },
2861                ..Default::default()
2862            },
2863            transactions: vec![tx3],
2864        };
2865
2866        let db = Database::default();
2867
2868        let mut setup = create_executor(db.clone(), Default::default());
2869
2870        setup.produce_and_commit(first_block).unwrap();
2871
2872        let producer = create_executor(db.clone(), Default::default());
2873
2874        let ExecutionResult {
2875            block: mut second_block,
2876            ..
2877        } = producer
2878            .produce_without_commit(second_block)
2879            .unwrap()
2880            .into_result();
2881        // Corrupt the utxo_id of the contract output
2882        if let Transaction::Script(script) = &mut second_block.transactions_mut()[0]
2883            && let Input::Contract(contract::Contract { utxo_id, .. }) =
2884                &mut script.inputs_mut()[0]
2885        {
2886            // use a previously valid contract id which isn't the correct one for this block
2887            *utxo_id = UtxoId::new(tx_id, 0);
2888        }
2889
2890        let verifier = create_executor(db, Default::default());
2891        let err = verifier.validate(&second_block).unwrap_err();
2892
2893        assert_eq!(
2894            err,
2895            ExecutorError::InvalidTransactionOutcome {
2896                transaction_id: tx_id
2897            }
2898        );
2899    }
2900
2901    #[test]
2902    fn outputs_with_amount_are_included_utxo_set() {
2903        let (deploy, script) = setup_executable_script();
2904        let script_id = script.id(&ChainId::default());
2905
2906        let database = &Database::default();
2907        let mut executor = create_executor(database.clone(), Default::default());
2908
2909        let block = PartialFuelBlock {
2910            header: Default::default(),
2911            transactions: vec![deploy.into(), script.into()],
2912        };
2913
2914        let ExecutionResult { block, .. } = executor.produce_and_commit(block).unwrap();
2915
2916        // ensure that all utxos with an amount are stored into the utxo set
2917        for (idx, output) in block.transactions()[1]
2918            .as_script()
2919            .unwrap()
2920            .outputs()
2921            .iter()
2922            .enumerate()
2923        {
2924            let id = UtxoId::new(script_id, idx as u16);
2925            match output {
2926                Output::Change { .. } | Output::Variable { .. } | Output::Coin { .. } => {
2927                    let maybe_utxo = database.storage::<Coins>().get(&id).unwrap();
2928                    assert!(maybe_utxo.is_some());
2929                    let utxo = maybe_utxo.unwrap();
2930                    assert!(*utxo.amount() > 0)
2931                }
2932                _ => (),
2933            }
2934        }
2935    }
2936
2937    #[test]
2938    fn outputs_with_no_value_are_excluded_from_utxo_set() {
2939        let mut rng = StdRng::seed_from_u64(2322);
2940        let asset_id: AssetId = rng.r#gen();
2941        let input_amount = 0;
2942        let coin_output_amount = 0;
2943
2944        let tx: Transaction = TxBuilder::new(2322)
2945            .coin_input(asset_id, input_amount)
2946            .variable_output(Default::default())
2947            .coin_output(asset_id, coin_output_amount)
2948            .change_output(asset_id)
2949            .build()
2950            .transaction()
2951            .clone()
2952            .into();
2953        let tx_id = tx.id(&ChainId::default());
2954
2955        let database = &Database::default();
2956        let mut executor = create_executor(database.clone(), Default::default());
2957
2958        let block = PartialFuelBlock {
2959            header: Default::default(),
2960            transactions: vec![tx],
2961        };
2962
2963        executor.produce_and_commit(block).unwrap();
2964
2965        for idx in 0..2 {
2966            let id = UtxoId::new(tx_id, idx);
2967            let maybe_utxo = database.storage::<Coins>().get(&id).unwrap();
2968            assert!(maybe_utxo.is_none());
2969        }
2970    }
2971
2972    fn message_from_input(input: &Input, da_height: u64) -> Message {
2973        MessageV1 {
2974            sender: *input.sender().unwrap(),
2975            recipient: *input.recipient().unwrap(),
2976            nonce: *input.nonce().unwrap(),
2977            amount: input.amount().unwrap(),
2978            data: input
2979                .input_data()
2980                .map(|data| data.to_vec())
2981                .unwrap_or_default(),
2982            da_height: DaBlockHeight(da_height),
2983        }
2984        .into()
2985    }
2986
2987    /// Helper to build transactions and a message in it for some of the message tests
2988    fn make_tx_and_message(rng: &mut StdRng, da_height: u64) -> (Transaction, Message) {
2989        let tx = TransactionBuilder::script(vec![], vec![])
2990            .add_unsigned_message_input(
2991                SecretKey::random(rng),
2992                rng.r#gen(),
2993                rng.r#gen(),
2994                1000,
2995                vec![],
2996            )
2997            .add_output(Output::change(rng.r#gen(), 1000, AssetId::BASE))
2998            .finalize();
2999
3000        let message = message_from_input(&tx.inputs()[0], da_height);
3001        (tx.into(), message)
3002    }
3003
3004    /// Helper to build database and executor for some of the message tests
3005    fn make_executor(messages: &[&Message]) -> Executor<Database, DisabledRelayer> {
3006        let mut database = Database::default();
3007        let database_ref = &mut database;
3008
3009        for message in messages {
3010            database_ref
3011                .storage::<Messages>()
3012                .insert(message.id(), message)
3013                .unwrap();
3014        }
3015
3016        create_executor(
3017            database,
3018            Config {
3019                forbid_fake_coins_default: true,
3020                ..Default::default()
3021            },
3022        )
3023    }
3024
3025    #[test]
3026    fn unspent_message_succeeds_when_msg_da_height_lt_block_da_height() {
3027        let mut rng = StdRng::seed_from_u64(2322);
3028
3029        let (tx, message) = make_tx_and_message(&mut rng, 0);
3030
3031        let block = PartialFuelBlock {
3032            header: Default::default(),
3033            transactions: vec![tx],
3034        };
3035
3036        let ExecutionResult { block, .. } = make_executor(&[&message])
3037            .produce_and_commit(block)
3038            .expect("block execution failed unexpectedly");
3039
3040        make_executor(&[&message])
3041            .validate_and_commit(&block)
3042            .expect("block validation failed unexpectedly");
3043    }
3044
3045    #[test]
3046    fn successful_execution_consume_all_messages() {
3047        let mut rng = StdRng::seed_from_u64(2322);
3048        let to: Address = rng.r#gen();
3049        let amount = 500;
3050
3051        let tx = TransactionBuilder::script(vec![], vec![])
3052            // Add `Input::MessageCoin`
3053            .add_unsigned_message_input(SecretKey::random(&mut rng), rng.r#gen(), rng.r#gen(), amount, vec![])
3054            // Add `Input::MessageData`
3055            .add_unsigned_message_input(SecretKey::random(&mut rng), rng.r#gen(), rng.r#gen(), amount, vec![0xff; 10])
3056            .add_output(Output::change(to, amount + amount, AssetId::BASE))
3057            .finalize();
3058        let tx_id = tx.id(&ChainId::default());
3059
3060        let message_coin = message_from_input(&tx.inputs()[0], 0);
3061        let message_data = message_from_input(&tx.inputs()[1], 0);
3062        let messages = vec![&message_coin, &message_data];
3063
3064        let block = PartialFuelBlock {
3065            header: Default::default(),
3066            transactions: vec![tx.into()],
3067        };
3068
3069        let mut exec = make_executor(&messages);
3070        let view = exec.storage_view_provider.latest_view().unwrap();
3071        assert!(view.message_exists(message_coin.nonce()).unwrap());
3072        assert!(view.message_exists(message_data.nonce()).unwrap());
3073
3074        let ExecutionResult {
3075            skipped_transactions,
3076            ..
3077        } = exec.produce_and_commit(block).unwrap();
3078        assert_eq!(skipped_transactions.len(), 0);
3079
3080        // Successful execution consumes `message_coin` and `message_data`.
3081        let view = exec.storage_view_provider.latest_view().unwrap();
3082        assert!(!view.message_exists(message_coin.nonce()).unwrap());
3083        assert!(!view.message_exists(message_data.nonce()).unwrap());
3084        assert_eq!(
3085            *view.coin(&UtxoId::new(tx_id, 0)).unwrap().amount(),
3086            amount + amount
3087        );
3088    }
3089
3090    #[test]
3091    fn reverted_execution_consume_only_message_coins() {
3092        let mut rng = StdRng::seed_from_u64(2322);
3093        let to: Address = rng.r#gen();
3094        let amount = 500;
3095
3096        // Script that return `1` - failed script -> execution result will be reverted.
3097        let script = vec![op::ret(1)].into_iter().collect();
3098        let tx = TransactionBuilder::script(script, vec![])
3099            // Add `Input::MessageCoin`
3100            .add_unsigned_message_input(SecretKey::random(&mut rng), rng.r#gen(), rng.r#gen(), amount, vec![])
3101            // Add `Input::MessageData`
3102            .add_unsigned_message_input(SecretKey::random(&mut rng), rng.r#gen(), rng.r#gen(), amount, vec![0xff; 10])
3103            .add_output(Output::change(to, amount + amount, AssetId::BASE))
3104            .finalize();
3105        let tx_id = tx.id(&ChainId::default());
3106
3107        let message_coin = message_from_input(&tx.inputs()[0], 0);
3108        let message_data = message_from_input(&tx.inputs()[1], 0);
3109        let messages = vec![&message_coin, &message_data];
3110
3111        let block = PartialFuelBlock {
3112            header: Default::default(),
3113            transactions: vec![tx.into()],
3114        };
3115
3116        let mut exec = make_executor(&messages);
3117        let view = exec.storage_view_provider.latest_view().unwrap();
3118        assert!(view.message_exists(message_coin.nonce()).unwrap());
3119        assert!(view.message_exists(message_data.nonce()).unwrap());
3120
3121        let ExecutionResult {
3122            skipped_transactions,
3123            ..
3124        } = exec.produce_and_commit(block).unwrap();
3125        assert_eq!(skipped_transactions.len(), 0);
3126
3127        // We should spend only `message_coin`. The `message_data` should be unspent.
3128        let view = exec.storage_view_provider.latest_view().unwrap();
3129        assert!(!view.message_exists(message_coin.nonce()).unwrap());
3130        assert!(view.message_exists(message_data.nonce()).unwrap());
3131        assert_eq!(*view.coin(&UtxoId::new(tx_id, 0)).unwrap().amount(), amount);
3132    }
3133
3134    #[test]
3135    fn message_fails_when_spending_nonexistent_message_id() {
3136        let mut rng = StdRng::seed_from_u64(2322);
3137
3138        let (tx, _message) = make_tx_and_message(&mut rng, 0);
3139
3140        let mut block = Block::default();
3141        *block.transactions_mut() = vec![tx.clone()];
3142
3143        let ExecutionResult {
3144            skipped_transactions,
3145            mut block,
3146            ..
3147        } = make_executor(&[]) // No messages in the db
3148            .produce_and_commit(block.clone().into())
3149            .unwrap();
3150        let err = &skipped_transactions[0].1;
3151        assert!(matches!(
3152            err,
3153            &ExecutorError::TransactionValidity(
3154                TransactionValidityError::MessageDoesNotExist(_)
3155            )
3156        ));
3157
3158        // Produced block is valid
3159        make_executor(&[]) // No messages in the db
3160            .validate_and_commit(&block)
3161            .unwrap();
3162
3163        // Invalidate block by returning back `tx` with not existing message
3164        let index = block.transactions().len() - 1;
3165        block.transactions_mut().insert(index, tx);
3166        let res = make_executor(&[]) // No messages in the db
3167            .validate_and_commit(&block);
3168        assert!(matches!(
3169            res,
3170            Err(ExecutorError::TransactionValidity(
3171                TransactionValidityError::MessageDoesNotExist(_)
3172            ))
3173        ));
3174    }
3175
3176    #[test]
3177    fn message_fails_when_spending_da_height_gt_block_da_height() {
3178        let mut rng = StdRng::seed_from_u64(2322);
3179
3180        let (tx, message) = make_tx_and_message(&mut rng, 1); // Block has zero da_height
3181
3182        let mut block = Block::default();
3183        *block.transactions_mut() = vec![tx.clone()];
3184
3185        let ExecutionResult {
3186            skipped_transactions,
3187            mut block,
3188            ..
3189        } = make_executor(&[&message])
3190            .produce_and_commit(block.clone().into())
3191            .unwrap();
3192        let err = &skipped_transactions[0].1;
3193        assert!(matches!(
3194            err,
3195            &ExecutorError::TransactionValidity(
3196                TransactionValidityError::MessageSpendTooEarly(_)
3197            )
3198        ));
3199
3200        // Produced block is valid
3201        make_executor(&[&message])
3202            .validate_and_commit(&block)
3203            .unwrap();
3204
3205        // Invalidate block by return back `tx` with not ready message.
3206        let index = block.transactions().len() - 1;
3207        block.transactions_mut().insert(index, tx);
3208        let res = make_executor(&[&message]).validate_and_commit(&block);
3209        assert!(matches!(
3210            res,
3211            Err(ExecutorError::TransactionValidity(
3212                TransactionValidityError::MessageSpendTooEarly(_)
3213            ))
3214        ));
3215    }
3216
3217    #[test]
3218    fn message_input_fails_when_mismatches_database() {
3219        let mut rng = StdRng::seed_from_u64(2322);
3220
3221        let (tx, mut message) = make_tx_and_message(&mut rng, 0);
3222
3223        // Modifying the message to make it mismatch
3224        message.set_amount(123);
3225
3226        let mut block = Block::default();
3227        *block.transactions_mut() = vec![tx.clone()];
3228
3229        let ExecutionResult {
3230            skipped_transactions,
3231            ..
3232        } = make_executor(&[&message])
3233            .produce_and_commit(block.clone().into())
3234            .unwrap();
3235        let err = &skipped_transactions[0].1;
3236        assert!(matches!(
3237            err,
3238            &ExecutorError::TransactionValidity(
3239                TransactionValidityError::MessageMismatch(_)
3240            )
3241        ));
3242    }
3243
3244    #[test]
3245    fn message_fails_when_spending_already_spent_message_id() {
3246        let mut rng = StdRng::seed_from_u64(2322);
3247
3248        // Create two transactions with the same message
3249        let (tx1, message) = make_tx_and_message(&mut rng, 0);
3250        let (mut tx2, _) = make_tx_and_message(&mut rng, 0);
3251        tx2.as_script_mut().unwrap().inputs_mut()[0] =
3252            tx1.as_script().unwrap().inputs()[0].clone();
3253
3254        let block = PartialFuelBlock {
3255            header: Default::default(),
3256            transactions: vec![tx1, tx2.clone()],
3257        };
3258
3259        let exec = make_executor(&[&message]);
3260        let ExecutionResult {
3261            skipped_transactions,
3262            mut block,
3263            ..
3264        } = exec.produce_without_commit(block).unwrap().into_result();
3265        // One of two transactions is skipped.
3266        assert_eq!(skipped_transactions.len(), 1);
3267        let err = &skipped_transactions[0].1;
3268        assert!(matches!(
3269            err,
3270            &ExecutorError::TransactionValidity(
3271                TransactionValidityError::MessageDoesNotExist(_)
3272            )
3273        ));
3274
3275        // Produced block is valid
3276        let exec = make_executor(&[&message]);
3277        let _ = exec.validate(&block).unwrap().into_result();
3278
3279        // Invalidate block by return back `tx2` transaction skipped during production.
3280        let len = block.transactions().len();
3281        block.transactions_mut().insert(len - 1, tx2);
3282        let exec = make_executor(&[&message]);
3283        let res = exec.validate(&block);
3284        assert!(matches!(
3285            res,
3286            Err(ExecutorError::TransactionValidity(
3287                TransactionValidityError::MessageDoesNotExist(_)
3288            ))
3289        ));
3290    }
3291
3292    #[test]
3293    fn withdrawal_message_included_in_header_for_successfully_executed_transaction() {
3294        // Given
3295        let amount_from_random_input = 1000;
3296        let smo_tx = TransactionBuilder::script(
3297            vec![
3298                // The amount to send in coins.
3299                op::movi(0x13, amount_from_random_input),
3300                // Send the message output.
3301                op::smo(0x0, 0x0, 0x0, 0x13),
3302                op::ret(RegId::ONE),
3303            ]
3304            .into_iter()
3305            .collect(),
3306            vec![],
3307        )
3308        .add_fee_input()
3309        .script_gas_limit(1000000)
3310        .finalize_as_transaction();
3311
3312        let block = PartialFuelBlock {
3313            header: Default::default(),
3314            transactions: vec![smo_tx],
3315        };
3316
3317        // When
3318        let ExecutionResult { block, .. } =
3319            create_executor(Default::default(), Default::default())
3320                .produce_and_commit(block)
3321                .expect("block execution failed unexpectedly");
3322        let result = create_executor(Default::default(), Default::default())
3323            .validate_and_commit(&block)
3324            .expect("block validation failed unexpectedly");
3325
3326        // Then
3327        let Some(Receipt::MessageOut {
3328            sender,
3329            recipient,
3330            amount,
3331            nonce,
3332            data,
3333            ..
3334        }) = result.tx_status[0].result.receipts().first().cloned()
3335        else {
3336            panic!("Expected a MessageOut receipt");
3337        };
3338
3339        // Reconstruct merkle message outbox merkle root  and see that it matches
3340        let mut mt = fuel_core_types::fuel_merkle::binary::in_memory::MerkleTree::new();
3341        mt.push(
3342            &Message::V1(MessageV1 {
3343                sender,
3344                recipient,
3345                nonce,
3346                amount,
3347                data: data.unwrap_or_default().into(),
3348                da_height: 1u64.into(),
3349            })
3350            .message_id()
3351            .to_bytes(),
3352        );
3353        assert_eq!(block.header().message_outbox_root().as_ref(), mt.root());
3354    }
3355
3356    #[test]
3357    fn withdrawal_message_not_included_in_header_for_failed_transaction() {
3358        // Given
3359        let amount_from_random_input = 1000;
3360        let smo_tx = TransactionBuilder::script(
3361            vec![
3362                // The amount to send in coins.
3363                op::movi(0x13, amount_from_random_input),
3364                // Send the message output.
3365                op::smo(0x0, 0x0, 0x0, 0x13),
3366                op::rvrt(0x0),
3367            ]
3368            .into_iter()
3369            .collect(),
3370            vec![],
3371        )
3372        .add_fee_input()
3373        .script_gas_limit(1000000)
3374        .finalize_as_transaction();
3375
3376        let block = PartialFuelBlock {
3377            header: Default::default(),
3378            transactions: vec![smo_tx],
3379        };
3380
3381        // When
3382        let ExecutionResult { block, .. } =
3383            create_executor(Default::default(), Default::default())
3384                .produce_and_commit(block)
3385                .expect("block execution failed unexpectedly");
3386        create_executor(Default::default(), Default::default())
3387            .validate_and_commit(&block)
3388            .expect("block validation failed unexpectedly");
3389
3390        // Then
3391        let empty_root = empty_sum_sha256();
3392        assert_eq!(block.header().message_outbox_root().as_ref(), empty_root)
3393    }
3394
3395    #[test]
3396    fn get_block_height_returns_current_executing_block() {
3397        let mut rng = StdRng::seed_from_u64(1234);
3398
3399        let base_asset_id = rng.r#gen();
3400
3401        // return current block height
3402        let script = vec![op::bhei(0x10), op::ret(0x10)];
3403        let tx = TransactionBuilder::script(script.into_iter().collect(), vec![])
3404            .script_gas_limit(10000)
3405            .add_unsigned_coin_input(
3406                SecretKey::random(&mut rng),
3407                rng.r#gen(),
3408                1000,
3409                base_asset_id,
3410                Default::default(),
3411            )
3412            .finalize();
3413
3414        // setup block
3415        let block_height = rng.gen_range(5u32..1000u32);
3416        let block_tx_idx = rng.r#gen();
3417
3418        let block = PartialFuelBlock {
3419            header: PartialBlockHeader {
3420                consensus: ConsensusHeader {
3421                    height: block_height.into(),
3422                    ..Default::default()
3423                },
3424                ..Default::default()
3425            },
3426            transactions: vec![tx.clone().into()],
3427        };
3428
3429        // setup db with coin to spend
3430        let database = &mut &mut Database::default();
3431        let coin_input = &tx.inputs()[0];
3432        let mut coin = CompressedCoin::default();
3433        coin.set_owner(*coin_input.input_owner().unwrap());
3434        coin.set_amount(coin_input.amount().unwrap());
3435        coin.set_asset_id(*coin_input.asset_id(&base_asset_id).unwrap());
3436        coin.set_tx_pointer(TxPointer::new(Default::default(), block_tx_idx));
3437        database
3438            .storage::<Coins>()
3439            .insert(coin_input.utxo_id().unwrap(), &coin)
3440            .unwrap();
3441
3442        // make executor with db
3443        let mut executor = create_executor(
3444            database.clone(),
3445            Config {
3446                forbid_fake_coins_default: true,
3447                ..Default::default()
3448            },
3449        );
3450
3451        let ExecutionResult { tx_status, .. } = executor
3452            .produce_and_commit(block)
3453            .expect("Should execute the block");
3454
3455        let receipts = tx_status[0].result.receipts();
3456        assert_eq!(block_height as u64, receipts[0].val().unwrap());
3457    }
3458
3459    #[test]
3460    fn get_time_returns_current_executing_block_time() {
3461        let mut rng = StdRng::seed_from_u64(1234);
3462
3463        let base_asset_id = rng.r#gen();
3464
3465        // return current block height
3466        let script = vec![op::bhei(0x10), op::time(0x11, 0x10), op::ret(0x11)];
3467        let tx = TransactionBuilder::script(script.into_iter().collect(), vec![])
3468            .script_gas_limit(10000)
3469            .add_unsigned_coin_input(
3470                SecretKey::random(&mut rng),
3471                rng.r#gen(),
3472                1000,
3473                base_asset_id,
3474                Default::default(),
3475            )
3476            .finalize();
3477
3478        // setup block
3479        let block_height = rng.gen_range(5u32..1000u32);
3480        let time = Tai64(rng.gen_range(1u32..u32::MAX) as u64);
3481
3482        let block = PartialFuelBlock {
3483            header: PartialBlockHeader {
3484                consensus: ConsensusHeader {
3485                    height: block_height.into(),
3486                    time,
3487                    ..Default::default()
3488                },
3489                ..Default::default()
3490            },
3491            transactions: vec![tx.clone().into()],
3492        };
3493
3494        // setup db with coin to spend
3495        let database = &mut &mut Database::default();
3496        let coin_input = &tx.inputs()[0];
3497        let mut coin = CompressedCoin::default();
3498        coin.set_owner(*coin_input.input_owner().unwrap());
3499        coin.set_amount(coin_input.amount().unwrap());
3500        coin.set_asset_id(*coin_input.asset_id(&base_asset_id).unwrap());
3501        database
3502            .storage::<Coins>()
3503            .insert(coin_input.utxo_id().unwrap(), &coin)
3504            .unwrap();
3505
3506        // make executor with db
3507        let mut executor = create_executor(
3508            database.clone(),
3509            Config {
3510                forbid_fake_coins_default: true,
3511                ..Default::default()
3512            },
3513        );
3514
3515        let ExecutionResult { tx_status, .. } = executor
3516            .produce_and_commit(block)
3517            .expect("Should execute the block");
3518
3519        let receipts = tx_status[0].result.receipts();
3520        assert_eq!(time.0, receipts[0].val().unwrap());
3521    }
3522
3523    #[test]
3524    fn tx_with_coin_predicate_included_by_block_producer_and_accepted_by_validator() {
3525        let mut rng = StdRng::seed_from_u64(2322u64);
3526        let predicate: Vec<u8> = vec![op::ret(RegId::ONE)].into_iter().collect();
3527        let owner = Input::predicate_owner(&predicate);
3528        let amount = 1000;
3529
3530        let consensus_parameters = ConsensusParameters::default();
3531        let config = Config {
3532            forbid_fake_coins_default: true,
3533            consensus_parameters: consensus_parameters.clone(),
3534        };
3535
3536        let mut tx = TransactionBuilder::script(
3537            vec![op::ret(RegId::ONE)].into_iter().collect(),
3538            vec![],
3539        )
3540        .max_fee_limit(amount)
3541        .add_input(Input::coin_predicate(
3542            rng.r#gen(),
3543            owner,
3544            amount,
3545            AssetId::BASE,
3546            rng.r#gen(),
3547            0,
3548            predicate,
3549            vec![],
3550        ))
3551        .add_output(Output::Change {
3552            to: Default::default(),
3553            amount: 0,
3554            asset_id: Default::default(),
3555        })
3556        .finalize();
3557        tx.estimate_predicates(
3558            &consensus_parameters.clone().into(),
3559            MemoryInstance::new(),
3560            &EmptyStorage,
3561        )
3562        .unwrap();
3563        let db = &mut Database::default();
3564
3565        // insert coin into state
3566        if let Input::CoinPredicate(CoinPredicate {
3567            utxo_id,
3568            owner,
3569            amount,
3570            asset_id,
3571            tx_pointer,
3572            ..
3573        }) = tx.inputs()[0]
3574        {
3575            let mut coin = CompressedCoin::default();
3576            coin.set_owner(owner);
3577            coin.set_amount(amount);
3578            coin.set_asset_id(asset_id);
3579            coin.set_tx_pointer(tx_pointer);
3580            db.storage::<Coins>().insert(&utxo_id, &coin).unwrap();
3581        }
3582
3583        let producer = create_executor(db.clone(), config.clone());
3584
3585        let ExecutionResult {
3586            block,
3587            skipped_transactions,
3588            ..
3589        } = producer
3590            .produce_without_commit_with_source_direct_resolve(Components {
3591                header_to_produce: PartialBlockHeader::default(),
3592                transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3593                coinbase_recipient: Default::default(),
3594                gas_price: 1,
3595            })
3596            .unwrap()
3597            .into_result();
3598        assert!(skipped_transactions.is_empty());
3599
3600        let validator = create_executor(db.clone(), config);
3601        let result = validator.validate(&block);
3602        assert!(result.is_ok(), "{result:?}")
3603    }
3604
3605    #[test]
3606    fn verifying_during_production_consensus_parameters_version_works() {
3607        let mut rng = StdRng::seed_from_u64(2322u64);
3608        let predicate: Vec<u8> = vec![op::ret(RegId::ONE)].into_iter().collect();
3609        let owner = Input::predicate_owner(&predicate);
3610        let amount = 1000;
3611        let cheap_consensus_parameters = ConsensusParameters::default();
3612
3613        let mut tx = TransactionBuilder::script(vec![], vec![])
3614            .max_fee_limit(amount)
3615            .add_input(Input::coin_predicate(
3616                rng.r#gen(),
3617                owner,
3618                amount,
3619                AssetId::BASE,
3620                rng.r#gen(),
3621                0,
3622                predicate,
3623                vec![],
3624            ))
3625            .finalize();
3626        tx.estimate_predicates(
3627            &cheap_consensus_parameters.clone().into(),
3628            MemoryInstance::new(),
3629            &EmptyStorage,
3630        )
3631        .unwrap();
3632
3633        // Given
3634        let gas_costs: GasCostsValues = GasCostsValuesV1 {
3635            vm_initialization: DependentCost::HeavyOperation {
3636                base: u32::MAX as u64,
3637                gas_per_unit: 0,
3638            },
3639            ..GasCostsValuesV1::free()
3640        }
3641        .into();
3642        let expensive_consensus_parameters_version = 0;
3643        let mut expensive_consensus_parameters = ConsensusParameters::default();
3644        expensive_consensus_parameters.set_gas_costs(gas_costs.into());
3645        // The block gas limit should cover `vm_initialization` cost
3646        expensive_consensus_parameters.set_block_gas_limit(u64::MAX);
3647        let config = Config {
3648            consensus_parameters: expensive_consensus_parameters.clone(),
3649            ..Default::default()
3650        };
3651        let producer = create_executor(Database::default(), config.clone());
3652
3653        let cheap_consensus_parameters_version = 1;
3654        let cheaply_checked_tx = MaybeCheckedTransaction::CheckedTransaction(
3655            tx.into_checked_basic(0u32.into(), &cheap_consensus_parameters)
3656                .unwrap()
3657                .into(),
3658            cheap_consensus_parameters_version,
3659        );
3660
3661        // When
3662        let ExecutionResult {
3663            skipped_transactions,
3664            ..
3665        } = producer
3666            .produce_without_commit_with_source_direct_resolve(Components {
3667                header_to_produce: PartialBlockHeader {
3668                    application: ApplicationHeader {
3669                        consensus_parameters_version:
3670                            expensive_consensus_parameters_version,
3671                        ..Default::default()
3672                    },
3673                    ..Default::default()
3674                },
3675                transactions_source: OnceTransactionsSource::new_maybe_checked(vec![
3676                    cheaply_checked_tx,
3677                ]),
3678                coinbase_recipient: Default::default(),
3679                gas_price: 1,
3680            })
3681            .unwrap()
3682            .into_result();
3683
3684        // Then
3685        assert_eq!(skipped_transactions.len(), 1);
3686        assert!(matches!(
3687            skipped_transactions[0].1,
3688            ExecutorError::InvalidTransaction(_)
3689        ));
3690    }
3691
3692    #[cfg(not(feature = "wasm-executor"))]
3693    #[tokio::test]
3694    async fn execute_block__new_transactions_trigger() {
3695        // Given
3696        struct MockNewTransactionsTrigger {
3697            sender: tokio::sync::mpsc::Sender<()>,
3698            counter: u8,
3699        }
3700
3701        impl NewTxWaiterPort for MockNewTransactionsTrigger {
3702            async fn wait_for_new_transactions(&mut self) -> WaitNewTransactionsResult {
3703                self.sender.send(()).await.unwrap();
3704                if self.counter == 0 {
3705                    self.counter += 1;
3706                    WaitNewTransactionsResult::NewTransaction
3707                } else {
3708                    WaitNewTransactionsResult::Timeout
3709                }
3710            }
3711        }
3712        let mut rng = StdRng::seed_from_u64(2322u64);
3713        let base_asset_id = rng.r#gen();
3714
3715        let tx = TransactionBuilder::script(vec![], vec![])
3716            .add_unsigned_coin_input(
3717                SecretKey::random(&mut rng),
3718                rng.r#gen(),
3719                1000,
3720                base_asset_id,
3721                Default::default(),
3722            )
3723            .finalize();
3724
3725        let config = Config {
3726            forbid_fake_coins_default: false,
3727            ..Default::default()
3728        };
3729        let (sender, mut receiver) = tokio::sync::mpsc::channel(2);
3730        let exec = create_executor(Database::default(), config.clone());
3731
3732        // When
3733        let res = exec
3734            .produce_without_commit_with_source(
3735                Components {
3736                    header_to_produce: Default::default(),
3737                    transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3738                    gas_price: 0,
3739                    coinbase_recipient: [1u8; 32].into(),
3740                },
3741                MockNewTransactionsTrigger { sender, counter: 0 },
3742                TransparentPreconfirmationSender,
3743            )
3744            .await
3745            .unwrap()
3746            .into_result();
3747
3748        // Then
3749        receiver.recv().await.unwrap();
3750        receiver.recv().await.unwrap();
3751        assert_eq!(res.skipped_transactions.len(), 0);
3752        assert_eq!(res.block.transactions().len(), 2);
3753    }
3754
3755    #[cfg(not(feature = "wasm-executor"))]
3756    #[tokio::test]
3757    async fn execute_block__send_preconfirmations() {
3758        // Given
3759        struct MockPreconfirmationsSender {
3760            sender: tokio::sync::mpsc::Sender<Vec<Preconfirmation>>,
3761        }
3762
3763        impl PreconfirmationSenderPort for MockPreconfirmationsSender {
3764            fn try_send(
3765                &self,
3766                preconfirmations: Vec<Preconfirmation>,
3767            ) -> Vec<Preconfirmation> {
3768                preconfirmations
3769            }
3770
3771            /// Send a batch of pre-confirmations, awaiting for the send to be successful.
3772            async fn send(&self, preconfirmations: Vec<Preconfirmation>) {
3773                self.sender.send(preconfirmations).await.unwrap();
3774            }
3775        }
3776        let mut rng = StdRng::seed_from_u64(2322u64);
3777        let base_asset_id = rng.r#gen();
3778
3779        let tx = TransactionBuilder::script(vec![], vec![])
3780            .add_unsigned_coin_input(
3781                SecretKey::random(&mut rng),
3782                rng.r#gen(),
3783                1000,
3784                base_asset_id,
3785                Default::default(),
3786            )
3787            .finalize();
3788
3789        let config = Config {
3790            forbid_fake_coins_default: false,
3791            ..Default::default()
3792        };
3793        let (sender, mut receiver) = tokio::sync::mpsc::channel(2);
3794        let exec = create_executor(Database::default(), config.clone());
3795
3796        // When
3797        let res = exec
3798            .produce_without_commit_with_source(
3799                Components {
3800                    header_to_produce: Default::default(),
3801                    transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3802                    gas_price: 0,
3803                    coinbase_recipient: [1u8; 32].into(),
3804                },
3805                TimeoutOnlyTxWaiter,
3806                MockPreconfirmationsSender { sender },
3807            )
3808            .await
3809            .unwrap()
3810            .into_result();
3811
3812        // Then
3813        let preconfirmations = receiver.recv().await.unwrap();
3814        assert_eq!(preconfirmations.len(), 1);
3815        assert!(matches!(
3816            preconfirmations[0].status,
3817            PreconfirmationStatus::Success { .. }
3818        ));
3819        assert_eq!(res.skipped_transactions.len(), 0);
3820        assert_eq!(res.block.transactions().len(), 2);
3821    }
3822
3823    #[tokio::test]
3824    async fn produce_without_commit_with_source__includes_a_mint_with_whatever_gas_price_provided()
3825     {
3826        use fuel_core_executor::executor::{
3827            TimeoutOnlyTxWaiter,
3828            TransparentPreconfirmationSender,
3829        };
3830
3831        // Given
3832        let mut rng = StdRng::seed_from_u64(2322u64);
3833        let base_asset_id = rng.r#gen();
3834        let gas_price = 1000;
3835
3836        let tx = TransactionBuilder::script(vec![], vec![])
3837            .add_unsigned_coin_input(
3838                SecretKey::random(&mut rng),
3839                rng.r#gen(),
3840                4321,
3841                base_asset_id,
3842                Default::default(),
3843            )
3844            .finalize();
3845
3846        let config = Config {
3847            forbid_fake_coins_default: false,
3848            ..Default::default()
3849        };
3850        let exec = create_executor(Database::default(), config.clone());
3851
3852        // When
3853        let res = exec
3854            .produce_without_commit_with_source(
3855                Components {
3856                    header_to_produce: Default::default(),
3857                    transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3858                    gas_price,
3859                    coinbase_recipient: [1u8; 32].into(),
3860                },
3861                TimeoutOnlyTxWaiter,
3862                TransparentPreconfirmationSender,
3863            )
3864            .await
3865            .unwrap()
3866            .into_result();
3867
3868        // Then
3869        let mint = res
3870            .block
3871            .transactions()
3872            .first()
3873            .expect("all blocks should have at least one tx (the mint)")
3874            .as_mint()
3875            .expect("the last tx should be a mint");
3876        assert_eq!(mint.gas_price(), &gas_price);
3877    }
3878
3879    #[tokio::test]
3880    async fn validate__will_fail_if_gas_price_does_not_match_expected_value() {
3881        use fuel_core_executor::executor::{
3882            TimeoutOnlyTxWaiter,
3883            TransparentPreconfirmationSender,
3884        };
3885
3886        // Given
3887        let mut rng = StdRng::seed_from_u64(2322u64);
3888        let base_asset_id = rng.r#gen();
3889        let gas_price = 1000;
3890
3891        let tx = TransactionBuilder::script(vec![], vec![])
3892            .add_unsigned_coin_input(
3893                SecretKey::random(&mut rng),
3894                rng.r#gen(),
3895                4321,
3896                base_asset_id,
3897                Default::default(),
3898            )
3899            .finalize();
3900
3901        let config = Config {
3902            forbid_fake_coins_default: false,
3903            ..Default::default()
3904        };
3905        let exec = create_executor(Database::default(), config.clone());
3906
3907        // When
3908        let res = exec
3909            .produce_without_commit_with_source(
3910                Components {
3911                    header_to_produce: Default::default(),
3912                    transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3913                    gas_price,
3914                    coinbase_recipient: [1u8; 32].into(),
3915                },
3916                TimeoutOnlyTxWaiter,
3917                TransparentPreconfirmationSender,
3918            )
3919            .await
3920            .unwrap()
3921            .into_result();
3922
3923        // Then
3924        let mut block = res.block;
3925        let mint = block.transactions_mut().first_mut().unwrap();
3926        let price = mint.as_mint_mut().unwrap().gas_price_mut();
3927        *price = gas_price * 2;
3928
3929        let res = exec.validate(&block);
3930        assert!(
3931            matches!(res, Err(ExecutorError::BlockMismatch)),
3932            "Expected BlockMismatch error, got: {res:?}"
3933        );
3934    }
3935
3936    #[test]
3937    #[cfg(not(feature = "wasm-executor"))]
3938    fn block_producer_never_includes_more_than_max_tx_count_transactions() {
3939        use fuel_core_executor::executor::max_tx_count;
3940
3941        let block_height = 1u32;
3942        let block_da_height = 2u64;
3943
3944        let mut consensus_parameters = ConsensusParameters::default();
3945
3946        // Given
3947        let transactions_in_tx_source = (max_tx_count() as usize) + 10;
3948        consensus_parameters.set_block_gas_limit(u64::MAX);
3949        let config = Config {
3950            consensus_parameters,
3951            ..Default::default()
3952        };
3953
3954        // When
3955        let block = test_block(
3956            block_height.into(),
3957            block_da_height.into(),
3958            transactions_in_tx_source,
3959        );
3960        let partial_fuel_block: PartialFuelBlock = block.into();
3961
3962        let producer = create_executor(Database::default(), config);
3963        let (result, _) = producer
3964            .produce_without_commit(partial_fuel_block)
3965            .unwrap()
3966            .into();
3967
3968        // Then
3969        assert_eq!(
3970            result.block.transactions().len(),
3971            (max_tx_count() as usize + 1)
3972        );
3973    }
3974
3975    #[test]
3976    #[cfg(not(feature = "wasm-executor"))]
3977    fn block_producer_never_includes_more_than_max_tx_count_transactions_with_bad_tx_source()
3978     {
3979        use fuel_core_executor::executor::max_tx_count;
3980        use std::sync::Mutex;
3981
3982        /// Bad transaction source: ignores the limit of `u16::MAX -1` transactions
3983        /// that should be returned by [`TransactionsSource::next()`].
3984        /// It is used only for testing purposes
3985        pub struct BadTransactionsSource {
3986            transactions: Mutex<Vec<MaybeCheckedTransaction>>,
3987        }
3988
3989        impl BadTransactionsSource {
3990            pub fn new(transactions: Vec<Transaction>) -> Self {
3991                Self {
3992                    transactions: Mutex::new(
3993                        transactions
3994                            .into_iter()
3995                            .map(MaybeCheckedTransaction::Transaction)
3996                            .collect(),
3997                    ),
3998                }
3999            }
4000        }
4001
4002        impl fuel_core_executor::ports::TransactionsSource for BadTransactionsSource {
4003            fn next(&self, _: u64, _: u16, _: u32) -> Vec<MaybeCheckedTransaction> {
4004                std::mem::take(&mut *self.transactions.lock().unwrap())
4005            }
4006        }
4007
4008        let block_height = 1u32;
4009        let block_da_height = 2u64;
4010
4011        let mut consensus_parameters = ConsensusParameters::default();
4012
4013        // Given
4014        let transactions_in_tx_source = (max_tx_count() as usize) + 10;
4015        consensus_parameters.set_block_gas_limit(u64::MAX);
4016        let config = Config {
4017            consensus_parameters,
4018            ..Default::default()
4019        };
4020
4021        let block = test_block(
4022            block_height.into(),
4023            block_da_height.into(),
4024            transactions_in_tx_source,
4025        );
4026        let partial_fuel_block: PartialFuelBlock = block.into();
4027        let components = Components {
4028            header_to_produce: partial_fuel_block.header,
4029            transactions_source: BadTransactionsSource::new(
4030                partial_fuel_block.transactions,
4031            ),
4032            coinbase_recipient: Default::default(),
4033            gas_price: 0,
4034        };
4035
4036        // When
4037        let producer = create_executor(Database::default(), config);
4038        let (result, _) = producer
4039            .produce_without_commit_with_source_direct_resolve(components)
4040            .unwrap()
4041            .into();
4042
4043        // Then
4044        assert_eq!(
4045            result.block.transactions().len(),
4046            (max_tx_count() as usize + 1)
4047        );
4048    }
4049
4050    #[cfg(feature = "relayer")]
4051    mod relayer {
4052        use super::*;
4053        use crate::database::database_description::{
4054            on_chain::OnChain,
4055            relayer::Relayer,
4056        };
4057        use fuel_core_relayer::storage::EventsHistory;
4058        use fuel_core_storage::{
4059            StorageAsMut,
4060            column::Column,
4061            iter::{
4062                IteratorOverTable,
4063                changes_iterator::ChangesIterator,
4064            },
4065            tables::FuelBlocks,
4066            transactional::StorageChanges,
4067        };
4068        use fuel_core_types::{
4069            entities::RelayedTransaction,
4070            fuel_merkle::binary::root_calculator::MerkleRootCalculator,
4071            fuel_tx::{
4072                Chargeable,
4073                output,
4074            },
4075            services::executor::ForcedTransactionFailure,
4076        };
4077
4078        fn database_with_genesis_block(da_block_height: u64) -> Database<OnChain> {
4079            let mut db = add_consensus_parameters(
4080                Database::default(),
4081                &ConsensusParameters::default(),
4082            );
4083            let mut block = Block::default();
4084            block.header_mut().set_da_height(da_block_height.into());
4085            block.header_mut().recalculate_metadata();
4086
4087            db.storage_as_mut::<FuelBlocks>()
4088                .insert(&0.into(), &block)
4089                .expect("Should insert genesis block without any problems");
4090            db
4091        }
4092
4093        fn add_message_to_relayer(db: &mut Database<Relayer>, message: Message) {
4094            let da_height = message.da_height();
4095            db.storage::<EventsHistory>()
4096                .insert(&da_height, &[Event::Message(message)])
4097                .expect("Should insert event");
4098        }
4099
4100        fn add_events_to_relayer(
4101            db: &mut Database<Relayer>,
4102            da_height: DaBlockHeight,
4103            events: &[Event],
4104        ) {
4105            db.storage::<EventsHistory>()
4106                .insert(&da_height, events)
4107                .expect("Should insert event");
4108        }
4109
4110        fn add_messages_to_relayer(db: &mut Database<Relayer>, relayer_da_height: u64) {
4111            for da_height in 0..=relayer_da_height {
4112                let mut message = Message::default();
4113                message.set_da_height(da_height.into());
4114                message.set_nonce(da_height.into());
4115
4116                add_message_to_relayer(db, message);
4117            }
4118        }
4119
4120        fn create_relayer_executor(
4121            on_chain: Database<OnChain>,
4122            relayer: Database<Relayer>,
4123        ) -> Executor<Database<OnChain>, Database<Relayer>> {
4124            Executor::new(on_chain, relayer, Default::default())
4125        }
4126
4127        struct Input {
4128            relayer_da_height: u64,
4129            block_height: u32,
4130            block_da_height: u64,
4131            genesis_da_height: Option<u64>,
4132        }
4133
4134        #[test_case::test_case(
4135            Input {
4136                relayer_da_height: 10,
4137                block_height: 1,
4138                block_da_height: 10,
4139                genesis_da_height: Some(0),
4140            } => matches Ok(()); "block producer takes all 10 messages from the relayer"
4141        )]
4142        #[test_case::test_case(
4143            Input {
4144                relayer_da_height: 10,
4145                block_height: 1,
4146                block_da_height: 5,
4147                genesis_da_height: Some(0),
4148            } => matches Ok(()); "block producer takes first 5 messages from the relayer"
4149        )]
4150        #[test_case::test_case(
4151            Input {
4152                relayer_da_height: 10,
4153                block_height: 1,
4154                block_da_height: 10,
4155                genesis_da_height: Some(5),
4156            } => matches Ok(()); "block producer takes last 5 messages from the relayer"
4157        )]
4158        #[test_case::test_case(
4159            Input {
4160                relayer_da_height: 10,
4161                block_height: 1,
4162                block_da_height: 10,
4163                genesis_da_height: Some(u64::MAX),
4164            } => matches Err(ExecutorError::DaHeightExceededItsLimit); "block producer fails when previous block exceeds `u64::MAX`"
4165        )]
4166        #[test_case::test_case(
4167            Input {
4168                relayer_da_height: 10,
4169                block_height: 1,
4170                block_da_height: 10,
4171                genesis_da_height: None,
4172            } => matches Err(ExecutorError::PreviousBlockIsNotFound); "block producer fails when previous block doesn't exist"
4173        )]
4174        #[test_case::test_case(
4175            Input {
4176                relayer_da_height: 10,
4177                block_height: 0,
4178                block_da_height: 10,
4179                genesis_da_height: Some(0),
4180            } => matches Err(ExecutorError::ExecutingGenesisBlock); "block producer fails when block height is zero"
4181        )]
4182        fn block_producer_takes_messages_from_the_relayer(
4183            input: Input,
4184        ) -> Result<(), ExecutorError> {
4185            let genesis_da_height = input.genesis_da_height.unwrap_or_default();
4186            let on_chain_db = if let Some(genesis_da_height) = input.genesis_da_height {
4187                database_with_genesis_block(genesis_da_height)
4188            } else {
4189                add_consensus_parameters(
4190                    Database::default(),
4191                    &ConsensusParameters::default(),
4192                )
4193            };
4194            let mut relayer_db = Database::<Relayer>::default();
4195
4196            // Given
4197            let relayer_da_height = input.relayer_da_height;
4198            let block_height = input.block_height;
4199            let block_da_height = input.block_da_height;
4200            add_messages_to_relayer(&mut relayer_db, relayer_da_height);
4201            assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
4202
4203            // When
4204            let producer = create_relayer_executor(on_chain_db, relayer_db);
4205            let block = test_block(block_height.into(), block_da_height.into(), 0);
4206            let (result, changes) = producer.produce_without_commit(block.into())?.into();
4207
4208            // Then
4209            let changes = StorageChanges::Changes(changes);
4210            let view = ChangesIterator::<Column>::new(&changes);
4211            assert_eq!(
4212                view.iter_all::<Messages>(None).count() as u64,
4213                block_da_height - genesis_da_height
4214            );
4215            assert_eq!(
4216                result.events.len() as u64,
4217                block_da_height - genesis_da_height
4218            );
4219            let messages = view.iter_all::<Messages>(None);
4220            for ((da_height, message), event) in (genesis_da_height + 1..block_da_height)
4221                .zip(messages)
4222                .zip(result.events.iter())
4223            {
4224                let (_, message) = message.unwrap();
4225                assert_eq!(message.da_height(), da_height.into());
4226                assert!(matches!(event, ExecutorEvent::MessageImported(_)));
4227            }
4228            Ok(())
4229        }
4230
4231        #[test]
4232        fn execute_without_commit__block_producer_includes_correct_inbox_event_merkle_root()
4233         {
4234            // given
4235            let genesis_da_height = 3u64;
4236            let on_chain_db = database_with_genesis_block(genesis_da_height);
4237            let mut relayer_db = Database::<Relayer>::default();
4238            let block_height = 1u32;
4239            let relayer_da_height = 10u64;
4240            let mut root_calculator = MerkleRootCalculator::new();
4241            for da_height in (genesis_da_height + 1)..=relayer_da_height {
4242                // message
4243                let mut message = Message::default();
4244                message.set_da_height(da_height.into());
4245                message.set_nonce(da_height.into());
4246                root_calculator.push(message.message_id().as_ref());
4247                // transaction
4248                let mut transaction = RelayedTransaction::default();
4249                transaction.set_nonce(da_height.into());
4250                transaction.set_da_height(da_height.into());
4251                transaction.set_max_gas(da_height);
4252                transaction.set_serialized_transaction(da_height.to_be_bytes().to_vec());
4253                root_calculator.push(Bytes32::from(transaction.id()).as_ref());
4254                // add events to relayer
4255                add_events_to_relayer(
4256                    &mut relayer_db,
4257                    da_height.into(),
4258                    &[message.into(), transaction.into()],
4259                );
4260            }
4261            let producer = create_relayer_executor(on_chain_db, relayer_db);
4262            let block = test_block(block_height.into(), relayer_da_height.into(), 0);
4263
4264            // when
4265            let (result, _) = producer
4266                .produce_without_commit(block.into())
4267                .unwrap()
4268                .into();
4269
4270            // then
4271            let expected = root_calculator.root().into();
4272            let actual = result.block.header().event_inbox_root();
4273            assert_eq!(actual, expected);
4274        }
4275
4276        #[test]
4277        fn execute_without_commit__relayed_tx_included_in_block() {
4278            let genesis_da_height = 3u64;
4279            let block_height = 1u32;
4280            let da_height = 10u64;
4281            let arb_large_max_gas = 10_000;
4282
4283            // given
4284            let relayer_db =
4285                relayer_db_with_valid_relayed_txs(da_height, arb_large_max_gas);
4286
4287            // when
4288            let on_chain_db = database_with_genesis_block(genesis_da_height);
4289            let producer = create_relayer_executor(on_chain_db, relayer_db);
4290            let block = test_block(block_height.into(), da_height.into(), 0);
4291            let (result, _) = producer
4292                .produce_without_commit(block.into())
4293                .unwrap()
4294                .into();
4295
4296            // then
4297            let txs = result.block.transactions();
4298            assert_eq!(txs.len(), 2);
4299        }
4300
4301        fn relayer_db_with_valid_relayed_txs(
4302            da_height: u64,
4303            max_gas: u64,
4304        ) -> Database<Relayer> {
4305            let mut relayed_tx = RelayedTransaction::default();
4306            let tx = script_tx_for_amount(100);
4307            let tx_bytes = tx.to_bytes();
4308            relayed_tx.set_serialized_transaction(tx_bytes);
4309            relayed_tx.set_max_gas(max_gas);
4310
4311            relayer_db_for_events(&[relayed_tx.into()], da_height)
4312        }
4313
4314        #[test]
4315        fn execute_without_commit_with_coinbase__relayed_tx_execute_and_mint_will_have_no_fees()
4316         {
4317            let genesis_da_height = 3u64;
4318            let block_height = 1u32;
4319            let da_height = 10u64;
4320            let gas_price = 1;
4321            let arb_max_gas = 10_000;
4322
4323            // given
4324            let relayer_db = relayer_db_with_valid_relayed_txs(da_height, arb_max_gas);
4325
4326            // when
4327            let on_chain_db = database_with_genesis_block(genesis_da_height);
4328            let producer = create_relayer_executor(on_chain_db, relayer_db);
4329            let block = test_block(block_height.into(), da_height.into(), 0);
4330            let (result, _) = producer
4331                .produce_without_commit_with_coinbase(
4332                    block.into(),
4333                    Default::default(),
4334                    gas_price,
4335                )
4336                .unwrap()
4337                .into();
4338
4339            // then
4340            let txs = result.block.transactions();
4341            assert_eq!(txs.len(), 2);
4342
4343            // and
4344            let mint = txs[1].as_mint().unwrap();
4345            assert_eq!(*mint.mint_amount(), 0);
4346        }
4347
4348        #[test]
4349        fn execute_without_commit__duplicated_relayed_tx_not_included_in_block() {
4350            let genesis_da_height = 3u64;
4351            let block_height = 1u32;
4352            let da_height = 10u64;
4353            let duplicate_count = 10;
4354            let arb_large_max_gas = 10_000;
4355
4356            // given
4357            let relayer_db = relayer_db_with_duplicate_valid_relayed_txs(
4358                da_height,
4359                duplicate_count,
4360                arb_large_max_gas,
4361            );
4362
4363            // when
4364            let on_chain_db = database_with_genesis_block(genesis_da_height);
4365            let producer = create_relayer_executor(on_chain_db, relayer_db);
4366            let block = test_block(block_height.into(), da_height.into(), 0);
4367            let (result, _) = producer
4368                .produce_without_commit(block.into())
4369                .unwrap()
4370                .into();
4371
4372            // then
4373            let txs = result.block.transactions();
4374            assert_eq!(txs.len(), 2);
4375
4376            // and
4377            let events = result.events;
4378            let count = events
4379                .into_iter()
4380                .filter(|event| {
4381                    matches!(event, ExecutorEvent::ForcedTransactionFailed { .. })
4382                })
4383                .count();
4384            assert_eq!(count, 10);
4385        }
4386
4387        fn relayer_db_with_duplicate_valid_relayed_txs(
4388            da_height: u64,
4389            duplicate_count: usize,
4390            max_gas: u64,
4391        ) -> Database<Relayer> {
4392            let mut relayed_tx = RelayedTransaction::default();
4393            let tx = script_tx_for_amount(100);
4394            let tx_bytes = tx.to_bytes();
4395            relayed_tx.set_serialized_transaction(tx_bytes);
4396            relayed_tx.set_max_gas(max_gas);
4397            let events = std::iter::repeat_n(relayed_tx.into(), duplicate_count + 1)
4398                .collect::<Vec<_>>();
4399
4400            relayer_db_for_events(&events, da_height)
4401        }
4402
4403        #[test]
4404        fn execute_without_commit__invalid_relayed_txs_are_not_included_and_are_reported()
4405        {
4406            let genesis_da_height = 3u64;
4407            let block_height = 1u32;
4408            let da_height = 10u64;
4409            let arb_large_max_gas = 10_000;
4410
4411            // given
4412            let relayer_db =
4413                relayer_db_with_invalid_relayed_txs(da_height, arb_large_max_gas);
4414
4415            // when
4416            let on_chain_db = database_with_genesis_block(genesis_da_height);
4417            let producer = create_relayer_executor(on_chain_db, relayer_db);
4418            let block = test_block(block_height.into(), da_height.into(), 0);
4419            let (result, _) = producer
4420                .produce_without_commit(block.into())
4421                .unwrap()
4422                .into();
4423
4424            // then
4425            let txs = result.block.transactions();
4426            assert_eq!(txs.len(), 1);
4427
4428            // and
4429            let events = result.events;
4430            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4431                failure: actual,
4432                ..
4433            } = &events[0]
4434            else {
4435                panic!("Expected `ForcedTransactionFailed` event")
4436            };
4437            let expected = &ForcedTransactionFailure::CheckError(CheckError::Validity(
4438                ValidityError::NoSpendableInput,
4439            ))
4440            .to_string();
4441            assert_eq!(expected, actual);
4442        }
4443
4444        fn relayer_db_with_invalid_relayed_txs(
4445            da_height: u64,
4446            max_gas: u64,
4447        ) -> Database<Relayer> {
4448            let event = arb_invalid_relayed_tx_event(max_gas);
4449            relayer_db_for_events(&[event], da_height)
4450        }
4451
4452        #[test]
4453        fn execute_without_commit__relayed_tx_with_low_max_gas_fails() {
4454            let genesis_da_height = 3u64;
4455            let block_height = 1u32;
4456            let da_height = 10u64;
4457            let zero_max_gas = 0;
4458
4459            // given
4460            let tx = script_tx_for_amount(100);
4461
4462            let relayer_db = relayer_db_with_specific_tx_for_relayed_tx(
4463                da_height,
4464                tx.clone(),
4465                zero_max_gas,
4466            );
4467
4468            // when
4469            let on_chain_db = database_with_genesis_block(genesis_da_height);
4470            let producer = create_relayer_executor(on_chain_db, relayer_db);
4471            let block = test_block(block_height.into(), da_height.into(), 0);
4472            let (result, _) = producer
4473                .produce_without_commit(block.into())
4474                .unwrap()
4475                .into();
4476
4477            // then
4478            let txs = result.block.transactions();
4479            assert_eq!(txs.len(), 1);
4480
4481            // and
4482            let consensus_params = ConsensusParameters::default();
4483            let actual_max_gas = tx
4484                .as_script()
4485                .unwrap()
4486                .max_gas(consensus_params.gas_costs(), consensus_params.fee_params());
4487            let events = result.events;
4488            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4489                failure: actual,
4490                ..
4491            } = &events[0]
4492            else {
4493                panic!("Expected `ForcedTransactionFailed` event")
4494            };
4495            let expected = &ForcedTransactionFailure::InsufficientMaxGas {
4496                claimed_max_gas: zero_max_gas,
4497                actual_max_gas,
4498            }
4499            .to_string();
4500            assert_eq!(expected, actual);
4501        }
4502
4503        fn relayer_db_with_specific_tx_for_relayed_tx(
4504            da_height: u64,
4505            tx: Transaction,
4506            max_gas: u64,
4507        ) -> Database<Relayer> {
4508            let mut relayed_tx = RelayedTransaction::default();
4509            let tx_bytes = tx.to_bytes();
4510            relayed_tx.set_serialized_transaction(tx_bytes);
4511            relayed_tx.set_max_gas(max_gas);
4512            relayer_db_for_events(&[relayed_tx.into()], da_height)
4513        }
4514
4515        #[test]
4516        fn execute_without_commit__relayed_tx_that_passes_checks_but_fails_execution_is_reported()
4517         {
4518            let genesis_da_height = 3u64;
4519            let block_height = 1u32;
4520            let da_height = 10u64;
4521            let arb_max_gas = 10_000;
4522
4523            // given
4524            let (tx_id, relayer_db) =
4525                tx_id_and_relayer_db_with_tx_that_passes_checks_but_fails_execution(
4526                    da_height,
4527                    arb_max_gas,
4528                );
4529
4530            // when
4531            let on_chain_db = database_with_genesis_block(genesis_da_height);
4532            let producer = create_relayer_executor(on_chain_db, relayer_db);
4533            let block = test_block(block_height.into(), da_height.into(), 0);
4534            let (result, _) = producer
4535                .produce_without_commit(block.into())
4536                .unwrap()
4537                .into();
4538
4539            // then
4540            let txs = result.block.transactions();
4541            assert_eq!(txs.len(), 2);
4542
4543            // and
4544            let events = result.events;
4545            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4546                failure: actual,
4547                ..
4548            } = &events[3]
4549            else {
4550                panic!("Expected `ForcedTransactionFailed` event")
4551            };
4552            let expected =
4553                &fuel_core_types::services::executor::Error::TransactionIdCollision(
4554                    tx_id,
4555                )
4556                .to_string();
4557            assert_eq!(expected, actual);
4558        }
4559
4560        fn tx_id_and_relayer_db_with_tx_that_passes_checks_but_fails_execution(
4561            da_height: u64,
4562            max_gas: u64,
4563        ) -> (Bytes32, Database<Relayer>) {
4564            let mut relayed_tx = RelayedTransaction::default();
4565            let tx = script_tx_for_amount(100);
4566            let tx_bytes = tx.to_bytes();
4567            relayed_tx.set_serialized_transaction(tx_bytes);
4568            relayed_tx.set_max_gas(max_gas);
4569            let mut bad_relayed_tx = relayed_tx.clone();
4570            let new_nonce = [9; 32].into();
4571            bad_relayed_tx.set_nonce(new_nonce);
4572            let relayer_db = relayer_db_for_events(
4573                &[relayed_tx.into(), bad_relayed_tx.into()],
4574                da_height,
4575            );
4576            (tx.id(&Default::default()), relayer_db)
4577        }
4578
4579        #[test]
4580        fn execute_without_commit__validation__includes_status_of_failed_relayed_tx() {
4581            let genesis_da_height = 3u64;
4582            let block_height = 1u32;
4583            let da_height = 10u64;
4584            let arb_large_max_gas = 10_000;
4585
4586            // given
4587            let event = arb_invalid_relayed_tx_event(arb_large_max_gas);
4588            let produced_block = produce_block_with_relayed_event(
4589                event.clone(),
4590                genesis_da_height,
4591                block_height,
4592                da_height,
4593            );
4594
4595            // when
4596            let verifier_db = database_with_genesis_block(genesis_da_height);
4597            let mut verifier_relayer_db = Database::<Relayer>::default();
4598            let events = vec![event];
4599            add_events_to_relayer(&mut verifier_relayer_db, da_height.into(), &events);
4600            let verifier = create_relayer_executor(verifier_db, verifier_relayer_db);
4601            let (result, _) = verifier.validate(&produced_block).unwrap().into();
4602
4603            // then
4604            let txs = produced_block.transactions();
4605            assert_eq!(txs.len(), 1);
4606
4607            // and
4608            let events = result.events;
4609            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4610                failure: actual,
4611                ..
4612            } = &events[0]
4613            else {
4614                panic!("Expected `ForcedTransactionFailed` event")
4615            };
4616            let expected = &ForcedTransactionFailure::CheckError(CheckError::Validity(
4617                ValidityError::NoSpendableInput,
4618            ))
4619            .to_string();
4620            assert_eq!(expected, actual);
4621        }
4622
4623        fn produce_block_with_relayed_event(
4624            event: Event,
4625            genesis_da_height: u64,
4626            block_height: u32,
4627            da_height: u64,
4628        ) -> Block {
4629            let producer_db = database_with_genesis_block(genesis_da_height);
4630            let producer_relayer_db = relayer_db_for_events(&[event], da_height);
4631
4632            let producer = create_relayer_executor(producer_db, producer_relayer_db);
4633            let block = test_block(block_height.into(), da_height.into(), 0);
4634            let (produced_result, _) = producer
4635                .produce_without_commit(block.into())
4636                .unwrap()
4637                .into();
4638            produced_result.block
4639        }
4640
4641        fn arb_invalid_relayed_tx_event(max_gas: u64) -> Event {
4642            let mut invalid_relayed_tx = RelayedTransaction::default();
4643            let mut tx = script_tx_for_amount(100);
4644            tx.as_script_mut().unwrap().inputs_mut().drain(..); // Remove all the inputs :)
4645            let tx_bytes = tx.to_bytes();
4646            invalid_relayed_tx.set_serialized_transaction(tx_bytes);
4647            invalid_relayed_tx.set_max_gas(max_gas);
4648            invalid_relayed_tx.into()
4649        }
4650
4651        #[test]
4652        fn execute_without_commit__relayed_mint_tx_not_included_in_block() {
4653            let genesis_da_height = 3u64;
4654            let block_height = 1u32;
4655            let da_height = 10u64;
4656            let tx_count = 0;
4657
4658            // given
4659            let relayer_db =
4660                relayer_db_with_mint_relayed_tx(da_height, block_height, tx_count);
4661
4662            // when
4663            let on_chain_db = database_with_genesis_block(genesis_da_height);
4664            let producer = create_relayer_executor(on_chain_db, relayer_db);
4665            let block =
4666                test_block(block_height.into(), da_height.into(), tx_count as usize);
4667            let (result, _) = producer
4668                .produce_without_commit(block.into())
4669                .unwrap()
4670                .into();
4671
4672            // then
4673            let txs = result.block.transactions();
4674            assert_eq!(txs.len(), 1);
4675
4676            // and
4677            let events = result.events;
4678            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4679                failure: actual,
4680                ..
4681            } = &events[0]
4682            else {
4683                panic!("Expected `ForcedTransactionFailed` event")
4684            };
4685            let expected = &ForcedTransactionFailure::InvalidTransactionType.to_string();
4686            assert_eq!(expected, actual);
4687        }
4688
4689        fn relayer_db_with_mint_relayed_tx(
4690            da_height: u64,
4691            block_height: u32,
4692            tx_count: u16,
4693        ) -> Database<Relayer> {
4694            let mut relayed_tx = RelayedTransaction::default();
4695            let base_asset_id = AssetId::BASE;
4696            let mint = Transaction::mint(
4697                TxPointer::new(block_height.into(), tx_count),
4698                contract::Contract {
4699                    utxo_id: UtxoId::new(Bytes32::zeroed(), 0),
4700                    balance_root: Bytes32::zeroed(),
4701                    state_root: Bytes32::zeroed(),
4702                    tx_pointer: TxPointer::new(BlockHeight::new(0), 0),
4703                    contract_id: ContractId::zeroed(),
4704                },
4705                output::contract::Contract {
4706                    input_index: 0,
4707                    balance_root: Bytes32::zeroed(),
4708                    state_root: Bytes32::zeroed(),
4709                },
4710                0,
4711                base_asset_id,
4712                0,
4713            );
4714            let tx = Transaction::Mint(mint);
4715            let tx_bytes = tx.to_bytes();
4716            relayed_tx.set_serialized_transaction(tx_bytes);
4717            relayer_db_for_events(&[relayed_tx.into()], da_height)
4718        }
4719
4720        fn relayer_db_for_events(events: &[Event], da_height: u64) -> Database<Relayer> {
4721            let mut relayer_db = Database::<Relayer>::default();
4722            add_events_to_relayer(&mut relayer_db, da_height.into(), events);
4723            relayer_db
4724        }
4725
4726        #[test]
4727        fn execute_without_commit__relayed_tx_can_spend_message_from_same_da_block() {
4728            let genesis_da_height = 3u64;
4729            let block_height = 1u32;
4730            let da_height = 10u64;
4731            let arb_max_gas = 10_000;
4732
4733            // given
4734            let relayer_db =
4735                relayer_db_with_relayed_tx_spending_message_from_same_da_block(
4736                    da_height,
4737                    arb_max_gas,
4738                );
4739
4740            // when
4741            let on_chain_db = database_with_genesis_block(genesis_da_height);
4742            let producer =
4743                create_relayer_executor(on_chain_db.clone(), relayer_db.clone());
4744            let block = test_block(block_height.into(), da_height.into(), 0);
4745            let (result, _) = producer
4746                .produce_without_commit(block.into())
4747                .unwrap()
4748                .into();
4749
4750            // then
4751            let txs = result.block.transactions();
4752            assert_eq!(txs.len(), 2);
4753
4754            let validator = create_relayer_executor(on_chain_db, relayer_db);
4755            // When
4756            let result = validator.validate(&result.block).map(|_| ());
4757
4758            // Then
4759            assert_eq!(Ok(()), result);
4760        }
4761
4762        fn relayer_db_with_relayed_tx_spending_message_from_same_da_block(
4763            da_height: u64,
4764            max_gas: u64,
4765        ) -> Database<Relayer> {
4766            let mut relayer_db = Database::<Relayer>::default();
4767            let mut message = Message::default();
4768            let nonce = 1.into();
4769            message.set_da_height(da_height.into());
4770            message.set_nonce(nonce);
4771            let message_event = Event::Message(message);
4772
4773            let mut relayed_tx = RelayedTransaction::default();
4774            let tx = TransactionBuilder::script(vec![], vec![])
4775                .script_gas_limit(10)
4776                .add_unsigned_message_input(
4777                    SecretKey::random(&mut StdRng::seed_from_u64(2322)),
4778                    Default::default(),
4779                    nonce,
4780                    Default::default(),
4781                    vec![],
4782                )
4783                .finalize_as_transaction();
4784            let tx_bytes = tx.to_bytes();
4785            relayed_tx.set_serialized_transaction(tx_bytes);
4786            relayed_tx.set_max_gas(max_gas);
4787            let tx_event = Event::Transaction(relayed_tx);
4788            add_events_to_relayer(
4789                &mut relayer_db,
4790                da_height.into(),
4791                &[message_event, tx_event],
4792            );
4793            relayer_db
4794        }
4795
4796        #[test]
4797        fn block_producer_does_not_take_messages_for_the_same_height() {
4798            let genesis_da_height = 1u64;
4799            let on_chain_db = database_with_genesis_block(genesis_da_height);
4800            let mut relayer_db = Database::<Relayer>::default();
4801
4802            // Given
4803            let relayer_da_height = 10u64;
4804            let block_height = 1u32;
4805            let block_da_height = 1u64;
4806            add_messages_to_relayer(&mut relayer_db, relayer_da_height);
4807            assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
4808
4809            // When
4810            let producer = create_relayer_executor(on_chain_db, relayer_db);
4811            let block = test_block(block_height.into(), block_da_height.into(), 10);
4812            let (result, changes) = producer
4813                .produce_without_commit(block.into())
4814                .unwrap()
4815                .into();
4816
4817            // Then
4818            let changes = StorageChanges::Changes(changes);
4819            let view = ChangesIterator::<Column>::new(&changes);
4820            assert!(result.skipped_transactions.is_empty());
4821            assert_eq!(view.iter_all::<Messages>(None).count() as u64, 0);
4822        }
4823
4824        #[test]
4825        fn block_producer_can_use_just_added_message_in_the_transaction() {
4826            let genesis_da_height = 1u64;
4827            let on_chain_db = database_with_genesis_block(genesis_da_height);
4828            let mut relayer_db = Database::<Relayer>::default();
4829
4830            let block_height = 1u32;
4831            let block_da_height = 2u64;
4832            let nonce = 1.into();
4833            let mut message = Message::default();
4834            message.set_da_height(block_da_height.into());
4835            message.set_nonce(nonce);
4836            add_message_to_relayer(&mut relayer_db, message);
4837
4838            // Given
4839            assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
4840            let tx = TransactionBuilder::script(vec![], vec![])
4841                .script_gas_limit(10)
4842                .add_unsigned_message_input(
4843                    SecretKey::random(&mut StdRng::seed_from_u64(2322)),
4844                    Default::default(),
4845                    nonce,
4846                    Default::default(),
4847                    vec![],
4848                )
4849                .finalize_as_transaction();
4850
4851            // When
4852            let mut block = test_block(block_height.into(), block_da_height.into(), 0);
4853            *block.transactions_mut() = vec![tx];
4854            let producer = create_relayer_executor(on_chain_db, relayer_db);
4855            let (result, changes) = producer
4856                .produce_without_commit(block.into())
4857                .unwrap()
4858                .into();
4859
4860            // Then
4861            let changes = StorageChanges::Changes(changes);
4862            let view = ChangesIterator::<Column>::new(&changes);
4863            assert!(result.skipped_transactions.is_empty());
4864            assert_eq!(view.iter_all::<Messages>(None).count() as u64, 0);
4865            assert_eq!(result.events.len(), 2);
4866            assert!(matches!(
4867                result.events[0],
4868                ExecutorEvent::MessageImported(_)
4869            ));
4870            assert!(matches!(
4871                result.events[1],
4872                ExecutorEvent::MessageConsumed(_)
4873            ));
4874        }
4875    }
4876}