Skip to main content

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, 0),
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, 0),
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, 0),
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, 0),
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 preconfirmation__tx_index_correct_for_first_transaction() {
3825        use fuel_core_executor::{
3826            executor::TimeoutOnlyTxWaiter,
3827            ports::PreconfirmationSenderPort,
3828        };
3829        use fuel_core_types::services::preconfirmation::{
3830            Preconfirmation,
3831            PreconfirmationStatus,
3832        };
3833
3834        // Given
3835        struct MockPreconfirmationsSender {
3836            sender: tokio::sync::mpsc::Sender<Vec<Preconfirmation>>,
3837        }
3838
3839        impl PreconfirmationSenderPort for MockPreconfirmationsSender {
3840            fn try_send(
3841                &self,
3842                preconfirmations: Vec<Preconfirmation>,
3843            ) -> Vec<Preconfirmation> {
3844                preconfirmations
3845            }
3846
3847            async fn send(&self, preconfirmations: Vec<Preconfirmation>) {
3848                self.sender.send(preconfirmations).await.unwrap();
3849            }
3850        }
3851
3852        let mut rng = StdRng::seed_from_u64(2322u64);
3853        let base_asset_id = rng.r#gen();
3854
3855        let tx = TransactionBuilder::script(vec![], vec![])
3856            .add_unsigned_coin_input(
3857                SecretKey::random(&mut rng),
3858                rng.r#gen(),
3859                1000,
3860                base_asset_id,
3861                Default::default(),
3862            )
3863            .finalize();
3864
3865        let tx_id = tx.id(&ChainId::default());
3866
3867        let config = Config {
3868            forbid_fake_coins_default: false,
3869            ..Default::default()
3870        };
3871        let (sender, mut receiver) = tokio::sync::mpsc::channel(2);
3872        let exec = create_executor(Database::default(), config.clone());
3873
3874        // When
3875        let _res = exec
3876            .produce_without_commit_with_source(
3877                Components {
3878                    header_to_produce: Default::default(),
3879                    transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3880                    gas_price: 0,
3881                    coinbase_recipient: [1u8; 32].into(),
3882                },
3883                TimeoutOnlyTxWaiter,
3884                MockPreconfirmationsSender { sender },
3885            )
3886            .await
3887            .unwrap()
3888            .into_result();
3889
3890        // Then
3891        let preconfirmations = receiver.recv().await.unwrap();
3892        // Find the preconfirmation for our transaction
3893        let our_preconfirmation = preconfirmations
3894            .iter()
3895            .find(|p| p.tx_id == tx_id)
3896            .expect("Should have preconfirmation for our transaction");
3897
3898        match &our_preconfirmation.status {
3899            PreconfirmationStatus::Success { tx_pointer, .. } => {
3900                assert_eq!(
3901                    tx_pointer.tx_index(),
3902                    0,
3903                    "First transaction should have index 0"
3904                );
3905            }
3906            status => panic!("Expected Success status, got: {:?}", status),
3907        }
3908    }
3909
3910    #[tokio::test]
3911    async fn preconfirmation__tx_indices_correct_for_multiple_transactions() {
3912        use fuel_core_executor::{
3913            executor::TimeoutOnlyTxWaiter,
3914            ports::PreconfirmationSenderPort,
3915        };
3916        use fuel_core_types::services::preconfirmation::{
3917            Preconfirmation,
3918            PreconfirmationStatus,
3919        };
3920
3921        // Given
3922        struct MockPreconfirmationsSender {
3923            sender: tokio::sync::mpsc::Sender<Vec<Preconfirmation>>,
3924        }
3925
3926        impl PreconfirmationSenderPort for MockPreconfirmationsSender {
3927            fn try_send(
3928                &self,
3929                preconfirmations: Vec<Preconfirmation>,
3930            ) -> Vec<Preconfirmation> {
3931                preconfirmations
3932            }
3933
3934            async fn send(&self, preconfirmations: Vec<Preconfirmation>) {
3935                self.sender.send(preconfirmations).await.unwrap();
3936            }
3937        }
3938
3939        let mut rng = StdRng::seed_from_u64(2322u64);
3940        let base_asset_id = rng.r#gen();
3941
3942        // Create 3 transactions with different script data to ensure unique tx ids
3943        let tx1 = TransactionBuilder::script(vec![], vec![])
3944            .add_unsigned_coin_input(
3945                SecretKey::random(&mut rng),
3946                rng.r#gen(),
3947                1000,
3948                base_asset_id,
3949                Default::default(),
3950            )
3951            .finalize();
3952
3953        let tx2 = TransactionBuilder::script(vec![], vec![1])
3954            .add_unsigned_coin_input(
3955                SecretKey::random(&mut rng),
3956                rng.r#gen(),
3957                1000,
3958                base_asset_id,
3959                Default::default(),
3960            )
3961            .finalize();
3962
3963        let tx3 = TransactionBuilder::script(vec![], vec![1, 2])
3964            .add_unsigned_coin_input(
3965                SecretKey::random(&mut rng),
3966                rng.r#gen(),
3967                1000,
3968                base_asset_id,
3969                Default::default(),
3970            )
3971            .finalize();
3972
3973        let tx1_id = tx1.id(&ChainId::default());
3974        let tx2_id = tx2.id(&ChainId::default());
3975        let tx3_id = tx3.id(&ChainId::default());
3976
3977        let config = Config {
3978            forbid_fake_coins_default: false,
3979            ..Default::default()
3980        };
3981        let (sender, mut receiver) = tokio::sync::mpsc::channel(2);
3982        let exec = create_executor(Database::default(), config.clone());
3983
3984        // When
3985        let _res = exec
3986            .produce_without_commit_with_source(
3987                Components {
3988                    header_to_produce: Default::default(),
3989                    transactions_source: OnceTransactionsSource::new(vec![
3990                        tx1.into(),
3991                        tx2.into(),
3992                        tx3.into(),
3993                    ]),
3994                    gas_price: 0,
3995                    coinbase_recipient: [1u8; 32].into(),
3996                },
3997                TimeoutOnlyTxWaiter,
3998                MockPreconfirmationsSender { sender },
3999            )
4000            .await
4001            .unwrap()
4002            .into_result();
4003
4004        // Then
4005        let preconfirmations = receiver.recv().await.unwrap();
4006
4007        // Find preconfirmations for our transactions
4008        let tx1_preconf = preconfirmations
4009            .iter()
4010            .find(|p| p.tx_id == tx1_id)
4011            .expect("Should have preconfirmation for tx1");
4012        let tx2_preconf = preconfirmations
4013            .iter()
4014            .find(|p| p.tx_id == tx2_id)
4015            .expect("Should have preconfirmation for tx2");
4016        let tx3_preconf = preconfirmations
4017            .iter()
4018            .find(|p| p.tx_id == tx3_id)
4019            .expect("Should have preconfirmation for tx3");
4020
4021        // Verify tx1 has index 0
4022        match &tx1_preconf.status {
4023            PreconfirmationStatus::Success { tx_pointer, .. } => {
4024                assert_eq!(
4025                    tx_pointer.tx_index(),
4026                    0,
4027                    "First transaction should have index 0"
4028                );
4029            }
4030            status => panic!("Expected Success status for tx1, got: {:?}", status),
4031        }
4032
4033        // Verify tx2 has index 1
4034        match &tx2_preconf.status {
4035            PreconfirmationStatus::Success { tx_pointer, .. } => {
4036                assert_eq!(
4037                    tx_pointer.tx_index(),
4038                    1,
4039                    "Second transaction should have index 1"
4040                );
4041            }
4042            status => panic!("Expected Success status for tx2, got: {:?}", status),
4043        }
4044
4045        // Verify tx3 has index 2
4046        match &tx3_preconf.status {
4047            PreconfirmationStatus::Success { tx_pointer, .. } => {
4048                assert_eq!(
4049                    tx_pointer.tx_index(),
4050                    2,
4051                    "Third transaction should have index 2"
4052                );
4053            }
4054            status => panic!("Expected Success status for tx3, got: {:?}", status),
4055        }
4056    }
4057
4058    #[tokio::test]
4059    async fn produce_without_commit_with_source__includes_a_mint_with_whatever_gas_price_provided()
4060     {
4061        use fuel_core_executor::executor::{
4062            TimeoutOnlyTxWaiter,
4063            TransparentPreconfirmationSender,
4064        };
4065
4066        // Given
4067        let mut rng = StdRng::seed_from_u64(2322u64);
4068        let base_asset_id = rng.r#gen();
4069        let gas_price = 1000;
4070
4071        let tx = TransactionBuilder::script(vec![], vec![])
4072            .add_unsigned_coin_input(
4073                SecretKey::random(&mut rng),
4074                rng.r#gen(),
4075                4321,
4076                base_asset_id,
4077                Default::default(),
4078            )
4079            .finalize();
4080
4081        let config = Config {
4082            forbid_fake_coins_default: false,
4083            ..Default::default()
4084        };
4085        let exec = create_executor(Database::default(), config.clone());
4086
4087        // When
4088        let res = exec
4089            .produce_without_commit_with_source(
4090                Components {
4091                    header_to_produce: Default::default(),
4092                    transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
4093                    gas_price,
4094                    coinbase_recipient: [1u8; 32].into(),
4095                },
4096                TimeoutOnlyTxWaiter,
4097                TransparentPreconfirmationSender,
4098            )
4099            .await
4100            .unwrap()
4101            .into_result();
4102
4103        // Then
4104        let mint = res
4105            .block
4106            .transactions()
4107            .first()
4108            .expect("all blocks should have at least one tx (the mint)")
4109            .as_mint()
4110            .expect("the last tx should be a mint");
4111        assert_eq!(mint.gas_price(), &gas_price);
4112    }
4113
4114    #[tokio::test]
4115    async fn validate__will_fail_if_gas_price_does_not_match_expected_value() {
4116        use fuel_core_executor::executor::{
4117            TimeoutOnlyTxWaiter,
4118            TransparentPreconfirmationSender,
4119        };
4120
4121        // Given
4122        let mut rng = StdRng::seed_from_u64(2322u64);
4123        let base_asset_id = rng.r#gen();
4124        let gas_price = 1000;
4125
4126        let tx = TransactionBuilder::script(vec![], vec![])
4127            .add_unsigned_coin_input(
4128                SecretKey::random(&mut rng),
4129                rng.r#gen(),
4130                4321,
4131                base_asset_id,
4132                Default::default(),
4133            )
4134            .finalize();
4135
4136        let config = Config {
4137            forbid_fake_coins_default: false,
4138            ..Default::default()
4139        };
4140        let exec = create_executor(Database::default(), config.clone());
4141
4142        // When
4143        let res = exec
4144            .produce_without_commit_with_source(
4145                Components {
4146                    header_to_produce: Default::default(),
4147                    transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
4148                    gas_price,
4149                    coinbase_recipient: [1u8; 32].into(),
4150                },
4151                TimeoutOnlyTxWaiter,
4152                TransparentPreconfirmationSender,
4153            )
4154            .await
4155            .unwrap()
4156            .into_result();
4157
4158        // Then
4159        let mut block = res.block;
4160        let mint = block.transactions_mut().first_mut().unwrap();
4161        let price = mint.as_mint_mut().unwrap().gas_price_mut();
4162        *price = gas_price * 2;
4163
4164        let res = exec.validate(&block);
4165        assert!(
4166            matches!(res, Err(ExecutorError::BlockMismatch)),
4167            "Expected BlockMismatch error, got: {res:?}"
4168        );
4169    }
4170
4171    #[test]
4172    #[cfg(not(feature = "wasm-executor"))]
4173    fn block_producer_never_includes_more_than_max_tx_count_transactions() {
4174        use fuel_core_executor::executor::max_tx_count;
4175
4176        let block_height = 1u32;
4177        let block_da_height = 2u64;
4178
4179        let mut consensus_parameters = ConsensusParameters::default();
4180
4181        // Given
4182        let transactions_in_tx_source = (max_tx_count() as usize) + 10;
4183        consensus_parameters.set_block_gas_limit(u64::MAX);
4184        let config = Config {
4185            consensus_parameters,
4186            ..Default::default()
4187        };
4188
4189        // When
4190        let block = test_block(
4191            block_height.into(),
4192            block_da_height.into(),
4193            transactions_in_tx_source,
4194        );
4195        let partial_fuel_block: PartialFuelBlock = block.into();
4196
4197        let producer = create_executor(Database::default(), config);
4198        let (result, _) = producer
4199            .produce_without_commit(partial_fuel_block)
4200            .unwrap()
4201            .into();
4202
4203        // Then
4204        assert_eq!(
4205            result.block.transactions().len(),
4206            (max_tx_count() as usize + 1)
4207        );
4208    }
4209
4210    #[test]
4211    #[cfg(not(feature = "wasm-executor"))]
4212    fn block_producer_never_includes_more_than_max_tx_count_transactions_with_bad_tx_source()
4213     {
4214        use fuel_core_executor::executor::max_tx_count;
4215        use std::sync::Mutex;
4216
4217        /// Bad transaction source: ignores the limit of `u16::MAX -1` transactions
4218        /// that should be returned by [`TransactionsSource::next()`].
4219        /// It is used only for testing purposes
4220        pub struct BadTransactionsSource {
4221            transactions: Mutex<Vec<MaybeCheckedTransaction>>,
4222        }
4223
4224        impl BadTransactionsSource {
4225            pub fn new(transactions: Vec<Transaction>) -> Self {
4226                Self {
4227                    transactions: Mutex::new(
4228                        transactions
4229                            .into_iter()
4230                            .map(MaybeCheckedTransaction::Transaction)
4231                            .collect(),
4232                    ),
4233                }
4234            }
4235        }
4236
4237        impl fuel_core_executor::ports::TransactionsSource for BadTransactionsSource {
4238            fn next(&self, _: u64, _: u16, _: u32) -> Vec<MaybeCheckedTransaction> {
4239                std::mem::take(&mut *self.transactions.lock().unwrap())
4240            }
4241        }
4242
4243        let block_height = 1u32;
4244        let block_da_height = 2u64;
4245
4246        let mut consensus_parameters = ConsensusParameters::default();
4247
4248        // Given
4249        let transactions_in_tx_source = (max_tx_count() as usize) + 10;
4250        consensus_parameters.set_block_gas_limit(u64::MAX);
4251        let config = Config {
4252            consensus_parameters,
4253            ..Default::default()
4254        };
4255
4256        let block = test_block(
4257            block_height.into(),
4258            block_da_height.into(),
4259            transactions_in_tx_source,
4260        );
4261        let partial_fuel_block: PartialFuelBlock = block.into();
4262        let components = Components {
4263            header_to_produce: partial_fuel_block.header,
4264            transactions_source: BadTransactionsSource::new(
4265                partial_fuel_block.transactions,
4266            ),
4267            coinbase_recipient: Default::default(),
4268            gas_price: 0,
4269        };
4270
4271        // When
4272        let producer = create_executor(Database::default(), config);
4273        let (result, _) = producer
4274            .produce_without_commit_with_source_direct_resolve(components)
4275            .unwrap()
4276            .into();
4277
4278        // Then
4279        assert_eq!(
4280            result.block.transactions().len(),
4281            (max_tx_count() as usize + 1)
4282        );
4283    }
4284
4285    #[cfg(feature = "relayer")]
4286    mod relayer {
4287        use super::*;
4288        use crate::database::database_description::{
4289            on_chain::OnChain,
4290            relayer::Relayer,
4291        };
4292        use fuel_core_relayer::storage::EventsHistory;
4293        use fuel_core_storage::{
4294            StorageAsMut,
4295            column::Column,
4296            iter::{
4297                IteratorOverTable,
4298                changes_iterator::ChangesIterator,
4299            },
4300            tables::FuelBlocks,
4301            transactional::StorageChanges,
4302        };
4303        use fuel_core_types::{
4304            entities::RelayedTransaction,
4305            fuel_merkle::binary::root_calculator::MerkleRootCalculator,
4306            fuel_tx::{
4307                Chargeable,
4308                output,
4309            },
4310            services::executor::ForcedTransactionFailure,
4311        };
4312
4313        fn database_with_genesis_block(da_block_height: u64) -> Database<OnChain> {
4314            let mut db = add_consensus_parameters(
4315                Database::default(),
4316                &ConsensusParameters::default(),
4317            );
4318            let mut block = Block::default();
4319            block.header_mut().set_da_height(da_block_height.into());
4320            block.header_mut().recalculate_metadata();
4321
4322            db.storage_as_mut::<FuelBlocks>()
4323                .insert(&0.into(), &block)
4324                .expect("Should insert genesis block without any problems");
4325            db
4326        }
4327
4328        fn add_message_to_relayer(db: &mut Database<Relayer>, message: Message) {
4329            let da_height = message.da_height();
4330            db.storage::<EventsHistory>()
4331                .insert(&da_height, &[Event::Message(message)])
4332                .expect("Should insert event");
4333        }
4334
4335        fn add_events_to_relayer(
4336            db: &mut Database<Relayer>,
4337            da_height: DaBlockHeight,
4338            events: &[Event],
4339        ) {
4340            db.storage::<EventsHistory>()
4341                .insert(&da_height, events)
4342                .expect("Should insert event");
4343        }
4344
4345        fn add_messages_to_relayer(db: &mut Database<Relayer>, relayer_da_height: u64) {
4346            for da_height in 0..=relayer_da_height {
4347                let mut message = Message::default();
4348                message.set_da_height(da_height.into());
4349                message.set_nonce(da_height.into());
4350
4351                add_message_to_relayer(db, message);
4352            }
4353        }
4354
4355        fn create_relayer_executor(
4356            on_chain: Database<OnChain>,
4357            relayer: Database<Relayer>,
4358        ) -> Executor<Database<OnChain>, Database<Relayer>> {
4359            Executor::new(on_chain, relayer, Default::default())
4360        }
4361
4362        struct Input {
4363            relayer_da_height: u64,
4364            block_height: u32,
4365            block_da_height: u64,
4366            genesis_da_height: Option<u64>,
4367        }
4368
4369        #[test_case::test_case(
4370            Input {
4371                relayer_da_height: 10,
4372                block_height: 1,
4373                block_da_height: 10,
4374                genesis_da_height: Some(0),
4375            } => matches Ok(()); "block producer takes all 10 messages from the relayer"
4376        )]
4377        #[test_case::test_case(
4378            Input {
4379                relayer_da_height: 10,
4380                block_height: 1,
4381                block_da_height: 5,
4382                genesis_da_height: Some(0),
4383            } => matches Ok(()); "block producer takes first 5 messages from the relayer"
4384        )]
4385        #[test_case::test_case(
4386            Input {
4387                relayer_da_height: 10,
4388                block_height: 1,
4389                block_da_height: 10,
4390                genesis_da_height: Some(5),
4391            } => matches Ok(()); "block producer takes last 5 messages from the relayer"
4392        )]
4393        #[test_case::test_case(
4394            Input {
4395                relayer_da_height: 10,
4396                block_height: 1,
4397                block_da_height: 10,
4398                genesis_da_height: Some(u64::MAX),
4399            } => matches Err(ExecutorError::DaHeightExceededItsLimit); "block producer fails when previous block exceeds `u64::MAX`"
4400        )]
4401        #[test_case::test_case(
4402            Input {
4403                relayer_da_height: 10,
4404                block_height: 1,
4405                block_da_height: 10,
4406                genesis_da_height: None,
4407            } => matches Err(ExecutorError::PreviousBlockIsNotFound); "block producer fails when previous block doesn't exist"
4408        )]
4409        #[test_case::test_case(
4410            Input {
4411                relayer_da_height: 10,
4412                block_height: 0,
4413                block_da_height: 10,
4414                genesis_da_height: Some(0),
4415            } => matches Err(ExecutorError::ExecutingGenesisBlock); "block producer fails when block height is zero"
4416        )]
4417        fn block_producer_takes_messages_from_the_relayer(
4418            input: Input,
4419        ) -> Result<(), ExecutorError> {
4420            let genesis_da_height = input.genesis_da_height.unwrap_or_default();
4421            let on_chain_db = if let Some(genesis_da_height) = input.genesis_da_height {
4422                database_with_genesis_block(genesis_da_height)
4423            } else {
4424                add_consensus_parameters(
4425                    Database::default(),
4426                    &ConsensusParameters::default(),
4427                )
4428            };
4429            let mut relayer_db = Database::<Relayer>::default();
4430
4431            // Given
4432            let relayer_da_height = input.relayer_da_height;
4433            let block_height = input.block_height;
4434            let block_da_height = input.block_da_height;
4435            add_messages_to_relayer(&mut relayer_db, relayer_da_height);
4436            assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
4437
4438            // When
4439            let producer = create_relayer_executor(on_chain_db, relayer_db);
4440            let block = test_block(block_height.into(), block_da_height.into(), 0);
4441            let (result, changes) = producer.produce_without_commit(block.into())?.into();
4442
4443            // Then
4444            let changes = StorageChanges::Changes(changes);
4445            let view = ChangesIterator::<Column>::new(&changes);
4446            assert_eq!(
4447                view.iter_all::<Messages>(None).count() as u64,
4448                block_da_height - genesis_da_height
4449            );
4450            assert_eq!(
4451                result.events.len() as u64,
4452                block_da_height - genesis_da_height
4453            );
4454            let messages = view.iter_all::<Messages>(None);
4455            for ((da_height, message), event) in (genesis_da_height + 1..block_da_height)
4456                .zip(messages)
4457                .zip(result.events.iter())
4458            {
4459                let (_, message) = message.unwrap();
4460                assert_eq!(message.da_height(), da_height.into());
4461                assert!(matches!(event, ExecutorEvent::MessageImported(_)));
4462            }
4463            Ok(())
4464        }
4465
4466        #[test]
4467        fn execute_without_commit__block_producer_includes_correct_inbox_event_merkle_root()
4468         {
4469            // given
4470            let genesis_da_height = 3u64;
4471            let on_chain_db = database_with_genesis_block(genesis_da_height);
4472            let mut relayer_db = Database::<Relayer>::default();
4473            let block_height = 1u32;
4474            let relayer_da_height = 10u64;
4475            let mut root_calculator = MerkleRootCalculator::new();
4476            for da_height in (genesis_da_height + 1)..=relayer_da_height {
4477                // message
4478                let mut message = Message::default();
4479                message.set_da_height(da_height.into());
4480                message.set_nonce(da_height.into());
4481                root_calculator.push(message.message_id().as_ref());
4482                // transaction
4483                let mut transaction = RelayedTransaction::default();
4484                transaction.set_nonce(da_height.into());
4485                transaction.set_da_height(da_height.into());
4486                transaction.set_max_gas(da_height);
4487                transaction.set_serialized_transaction(da_height.to_be_bytes().to_vec());
4488                root_calculator.push(Bytes32::from(transaction.id()).as_ref());
4489                // add events to relayer
4490                add_events_to_relayer(
4491                    &mut relayer_db,
4492                    da_height.into(),
4493                    &[message.into(), transaction.into()],
4494                );
4495            }
4496            let producer = create_relayer_executor(on_chain_db, relayer_db);
4497            let block = test_block(block_height.into(), relayer_da_height.into(), 0);
4498
4499            // when
4500            let (result, _) = producer
4501                .produce_without_commit(block.into())
4502                .unwrap()
4503                .into();
4504
4505            // then
4506            let expected = root_calculator.root().into();
4507            let actual = result.block.header().event_inbox_root();
4508            assert_eq!(actual, expected);
4509        }
4510
4511        #[test]
4512        fn execute_without_commit__relayed_tx_included_in_block() {
4513            let genesis_da_height = 3u64;
4514            let block_height = 1u32;
4515            let da_height = 10u64;
4516            let arb_large_max_gas = 10_000;
4517
4518            // given
4519            let relayer_db =
4520                relayer_db_with_valid_relayed_txs(da_height, arb_large_max_gas);
4521
4522            // when
4523            let on_chain_db = database_with_genesis_block(genesis_da_height);
4524            let producer = create_relayer_executor(on_chain_db, relayer_db);
4525            let block = test_block(block_height.into(), da_height.into(), 0);
4526            let (result, _) = producer
4527                .produce_without_commit(block.into())
4528                .unwrap()
4529                .into();
4530
4531            // then
4532            let txs = result.block.transactions();
4533            assert_eq!(txs.len(), 2);
4534        }
4535
4536        fn relayer_db_with_valid_relayed_txs(
4537            da_height: u64,
4538            max_gas: u64,
4539        ) -> Database<Relayer> {
4540            let mut relayed_tx = RelayedTransaction::default();
4541            let tx = script_tx_for_amount(100);
4542            let tx_bytes = tx.to_bytes();
4543            relayed_tx.set_serialized_transaction(tx_bytes);
4544            relayed_tx.set_max_gas(max_gas);
4545
4546            relayer_db_for_events(&[relayed_tx.into()], da_height)
4547        }
4548
4549        #[test]
4550        fn execute_without_commit_with_coinbase__relayed_tx_execute_and_mint_will_have_no_fees()
4551         {
4552            let genesis_da_height = 3u64;
4553            let block_height = 1u32;
4554            let da_height = 10u64;
4555            let gas_price = 1;
4556            let arb_max_gas = 10_000;
4557
4558            // given
4559            let relayer_db = relayer_db_with_valid_relayed_txs(da_height, arb_max_gas);
4560
4561            // when
4562            let on_chain_db = database_with_genesis_block(genesis_da_height);
4563            let producer = create_relayer_executor(on_chain_db, relayer_db);
4564            let block = test_block(block_height.into(), da_height.into(), 0);
4565            let (result, _) = producer
4566                .produce_without_commit_with_coinbase(
4567                    block.into(),
4568                    Default::default(),
4569                    gas_price,
4570                )
4571                .unwrap()
4572                .into();
4573
4574            // then
4575            let txs = result.block.transactions();
4576            assert_eq!(txs.len(), 2);
4577
4578            // and
4579            let mint = txs[1].as_mint().unwrap();
4580            assert_eq!(*mint.mint_amount(), 0);
4581        }
4582
4583        #[test]
4584        fn execute_without_commit__duplicated_relayed_tx_not_included_in_block() {
4585            let genesis_da_height = 3u64;
4586            let block_height = 1u32;
4587            let da_height = 10u64;
4588            let duplicate_count = 10;
4589            let arb_large_max_gas = 10_000;
4590
4591            // given
4592            let relayer_db = relayer_db_with_duplicate_valid_relayed_txs(
4593                da_height,
4594                duplicate_count,
4595                arb_large_max_gas,
4596            );
4597
4598            // when
4599            let on_chain_db = database_with_genesis_block(genesis_da_height);
4600            let producer = create_relayer_executor(on_chain_db, relayer_db);
4601            let block = test_block(block_height.into(), da_height.into(), 0);
4602            let (result, _) = producer
4603                .produce_without_commit(block.into())
4604                .unwrap()
4605                .into();
4606
4607            // then
4608            let txs = result.block.transactions();
4609            assert_eq!(txs.len(), 2);
4610
4611            // and
4612            let events = result.events;
4613            let count = events
4614                .into_iter()
4615                .filter(|event| {
4616                    matches!(event, ExecutorEvent::ForcedTransactionFailed { .. })
4617                })
4618                .count();
4619            assert_eq!(count, 10);
4620        }
4621
4622        fn relayer_db_with_duplicate_valid_relayed_txs(
4623            da_height: u64,
4624            duplicate_count: usize,
4625            max_gas: u64,
4626        ) -> Database<Relayer> {
4627            let mut relayed_tx = RelayedTransaction::default();
4628            let tx = script_tx_for_amount(100);
4629            let tx_bytes = tx.to_bytes();
4630            relayed_tx.set_serialized_transaction(tx_bytes);
4631            relayed_tx.set_max_gas(max_gas);
4632            let events = std::iter::repeat_n(relayed_tx.into(), duplicate_count + 1)
4633                .collect::<Vec<_>>();
4634
4635            relayer_db_for_events(&events, da_height)
4636        }
4637
4638        #[test]
4639        fn execute_without_commit__invalid_relayed_txs_are_not_included_and_are_reported()
4640        {
4641            let genesis_da_height = 3u64;
4642            let block_height = 1u32;
4643            let da_height = 10u64;
4644            let arb_large_max_gas = 10_000;
4645
4646            // given
4647            let relayer_db =
4648                relayer_db_with_invalid_relayed_txs(da_height, arb_large_max_gas);
4649
4650            // when
4651            let on_chain_db = database_with_genesis_block(genesis_da_height);
4652            let producer = create_relayer_executor(on_chain_db, relayer_db);
4653            let block = test_block(block_height.into(), da_height.into(), 0);
4654            let (result, _) = producer
4655                .produce_without_commit(block.into())
4656                .unwrap()
4657                .into();
4658
4659            // then
4660            let txs = result.block.transactions();
4661            assert_eq!(txs.len(), 1);
4662
4663            // and
4664            let events = result.events;
4665            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4666                failure: actual,
4667                ..
4668            } = &events[0]
4669            else {
4670                panic!("Expected `ForcedTransactionFailed` event")
4671            };
4672            let expected = &ForcedTransactionFailure::CheckError(CheckError::Validity(
4673                ValidityError::NoSpendableInput,
4674            ))
4675            .to_string();
4676            assert_eq!(expected, actual);
4677        }
4678
4679        fn relayer_db_with_invalid_relayed_txs(
4680            da_height: u64,
4681            max_gas: u64,
4682        ) -> Database<Relayer> {
4683            let event = arb_invalid_relayed_tx_event(max_gas);
4684            relayer_db_for_events(&[event], da_height)
4685        }
4686
4687        #[test]
4688        fn execute_without_commit__relayed_tx_with_low_max_gas_fails() {
4689            let genesis_da_height = 3u64;
4690            let block_height = 1u32;
4691            let da_height = 10u64;
4692            let zero_max_gas = 0;
4693
4694            // given
4695            let tx = script_tx_for_amount(100);
4696
4697            let relayer_db = relayer_db_with_specific_tx_for_relayed_tx(
4698                da_height,
4699                tx.clone(),
4700                zero_max_gas,
4701            );
4702
4703            // when
4704            let on_chain_db = database_with_genesis_block(genesis_da_height);
4705            let producer = create_relayer_executor(on_chain_db, relayer_db);
4706            let block = test_block(block_height.into(), da_height.into(), 0);
4707            let (result, _) = producer
4708                .produce_without_commit(block.into())
4709                .unwrap()
4710                .into();
4711
4712            // then
4713            let txs = result.block.transactions();
4714            assert_eq!(txs.len(), 1);
4715
4716            // and
4717            let consensus_params = ConsensusParameters::default();
4718            let actual_max_gas = tx
4719                .as_script()
4720                .unwrap()
4721                .max_gas(consensus_params.gas_costs(), consensus_params.fee_params());
4722            let events = result.events;
4723            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4724                failure: actual,
4725                ..
4726            } = &events[0]
4727            else {
4728                panic!("Expected `ForcedTransactionFailed` event")
4729            };
4730            let expected = &ForcedTransactionFailure::InsufficientMaxGas {
4731                claimed_max_gas: zero_max_gas,
4732                actual_max_gas,
4733            }
4734            .to_string();
4735            assert_eq!(expected, actual);
4736        }
4737
4738        fn relayer_db_with_specific_tx_for_relayed_tx(
4739            da_height: u64,
4740            tx: Transaction,
4741            max_gas: u64,
4742        ) -> Database<Relayer> {
4743            let mut relayed_tx = RelayedTransaction::default();
4744            let tx_bytes = tx.to_bytes();
4745            relayed_tx.set_serialized_transaction(tx_bytes);
4746            relayed_tx.set_max_gas(max_gas);
4747            relayer_db_for_events(&[relayed_tx.into()], da_height)
4748        }
4749
4750        #[test]
4751        fn execute_without_commit__relayed_tx_that_passes_checks_but_fails_execution_is_reported()
4752         {
4753            let genesis_da_height = 3u64;
4754            let block_height = 1u32;
4755            let da_height = 10u64;
4756            let arb_max_gas = 10_000;
4757
4758            // given
4759            let (tx_id, relayer_db) =
4760                tx_id_and_relayer_db_with_tx_that_passes_checks_but_fails_execution(
4761                    da_height,
4762                    arb_max_gas,
4763                );
4764
4765            // when
4766            let on_chain_db = database_with_genesis_block(genesis_da_height);
4767            let producer = create_relayer_executor(on_chain_db, relayer_db);
4768            let block = test_block(block_height.into(), da_height.into(), 0);
4769            let (result, _) = producer
4770                .produce_without_commit(block.into())
4771                .unwrap()
4772                .into();
4773
4774            // then
4775            let txs = result.block.transactions();
4776            assert_eq!(txs.len(), 2);
4777
4778            // and
4779            let events = result.events;
4780            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4781                failure: actual,
4782                ..
4783            } = &events[3]
4784            else {
4785                panic!("Expected `ForcedTransactionFailed` event")
4786            };
4787            let expected =
4788                &fuel_core_types::services::executor::Error::TransactionIdCollision(
4789                    tx_id,
4790                )
4791                .to_string();
4792            assert_eq!(expected, actual);
4793        }
4794
4795        fn tx_id_and_relayer_db_with_tx_that_passes_checks_but_fails_execution(
4796            da_height: u64,
4797            max_gas: u64,
4798        ) -> (Bytes32, Database<Relayer>) {
4799            let mut relayed_tx = RelayedTransaction::default();
4800            let tx = script_tx_for_amount(100);
4801            let tx_bytes = tx.to_bytes();
4802            relayed_tx.set_serialized_transaction(tx_bytes);
4803            relayed_tx.set_max_gas(max_gas);
4804            let mut bad_relayed_tx = relayed_tx.clone();
4805            let new_nonce = [9; 32].into();
4806            bad_relayed_tx.set_nonce(new_nonce);
4807            let relayer_db = relayer_db_for_events(
4808                &[relayed_tx.into(), bad_relayed_tx.into()],
4809                da_height,
4810            );
4811            (tx.id(&Default::default()), relayer_db)
4812        }
4813
4814        #[test]
4815        fn execute_without_commit__validation__includes_status_of_failed_relayed_tx() {
4816            let genesis_da_height = 3u64;
4817            let block_height = 1u32;
4818            let da_height = 10u64;
4819            let arb_large_max_gas = 10_000;
4820
4821            // given
4822            let event = arb_invalid_relayed_tx_event(arb_large_max_gas);
4823            let produced_block = produce_block_with_relayed_event(
4824                event.clone(),
4825                genesis_da_height,
4826                block_height,
4827                da_height,
4828            );
4829
4830            // when
4831            let verifier_db = database_with_genesis_block(genesis_da_height);
4832            let mut verifier_relayer_db = Database::<Relayer>::default();
4833            let events = vec![event];
4834            add_events_to_relayer(&mut verifier_relayer_db, da_height.into(), &events);
4835            let verifier = create_relayer_executor(verifier_db, verifier_relayer_db);
4836            let (result, _) = verifier.validate(&produced_block).unwrap().into();
4837
4838            // then
4839            let txs = produced_block.transactions();
4840            assert_eq!(txs.len(), 1);
4841
4842            // and
4843            let events = result.events;
4844            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4845                failure: actual,
4846                ..
4847            } = &events[0]
4848            else {
4849                panic!("Expected `ForcedTransactionFailed` event")
4850            };
4851            let expected = &ForcedTransactionFailure::CheckError(CheckError::Validity(
4852                ValidityError::NoSpendableInput,
4853            ))
4854            .to_string();
4855            assert_eq!(expected, actual);
4856        }
4857
4858        fn produce_block_with_relayed_event(
4859            event: Event,
4860            genesis_da_height: u64,
4861            block_height: u32,
4862            da_height: u64,
4863        ) -> Block {
4864            let producer_db = database_with_genesis_block(genesis_da_height);
4865            let producer_relayer_db = relayer_db_for_events(&[event], da_height);
4866
4867            let producer = create_relayer_executor(producer_db, producer_relayer_db);
4868            let block = test_block(block_height.into(), da_height.into(), 0);
4869            let (produced_result, _) = producer
4870                .produce_without_commit(block.into())
4871                .unwrap()
4872                .into();
4873            produced_result.block
4874        }
4875
4876        fn arb_invalid_relayed_tx_event(max_gas: u64) -> Event {
4877            let mut invalid_relayed_tx = RelayedTransaction::default();
4878            let mut tx = script_tx_for_amount(100);
4879            tx.as_script_mut().unwrap().inputs_mut().drain(..); // Remove all the inputs :)
4880            let tx_bytes = tx.to_bytes();
4881            invalid_relayed_tx.set_serialized_transaction(tx_bytes);
4882            invalid_relayed_tx.set_max_gas(max_gas);
4883            invalid_relayed_tx.into()
4884        }
4885
4886        #[test]
4887        fn execute_without_commit__relayed_mint_tx_not_included_in_block() {
4888            let genesis_da_height = 3u64;
4889            let block_height = 1u32;
4890            let da_height = 10u64;
4891            let tx_count = 0;
4892
4893            // given
4894            let relayer_db =
4895                relayer_db_with_mint_relayed_tx(da_height, block_height, tx_count);
4896
4897            // when
4898            let on_chain_db = database_with_genesis_block(genesis_da_height);
4899            let producer = create_relayer_executor(on_chain_db, relayer_db);
4900            let block =
4901                test_block(block_height.into(), da_height.into(), tx_count as usize);
4902            let (result, _) = producer
4903                .produce_without_commit(block.into())
4904                .unwrap()
4905                .into();
4906
4907            // then
4908            let txs = result.block.transactions();
4909            assert_eq!(txs.len(), 1);
4910
4911            // and
4912            let events = result.events;
4913            let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4914                failure: actual,
4915                ..
4916            } = &events[0]
4917            else {
4918                panic!("Expected `ForcedTransactionFailed` event")
4919            };
4920            let expected = &ForcedTransactionFailure::InvalidTransactionType.to_string();
4921            assert_eq!(expected, actual);
4922        }
4923
4924        fn relayer_db_with_mint_relayed_tx(
4925            da_height: u64,
4926            block_height: u32,
4927            tx_count: u16,
4928        ) -> Database<Relayer> {
4929            let mut relayed_tx = RelayedTransaction::default();
4930            let base_asset_id = AssetId::BASE;
4931            let mint = Transaction::mint(
4932                TxPointer::new(block_height.into(), tx_count),
4933                contract::Contract {
4934                    utxo_id: UtxoId::new(Bytes32::zeroed(), 0),
4935                    balance_root: Bytes32::zeroed(),
4936                    state_root: Bytes32::zeroed(),
4937                    tx_pointer: TxPointer::new(BlockHeight::new(0), 0),
4938                    contract_id: ContractId::zeroed(),
4939                },
4940                output::contract::Contract {
4941                    input_index: 0,
4942                    balance_root: Bytes32::zeroed(),
4943                    state_root: Bytes32::zeroed(),
4944                },
4945                0,
4946                base_asset_id,
4947                0,
4948            );
4949            let tx = Transaction::Mint(mint);
4950            let tx_bytes = tx.to_bytes();
4951            relayed_tx.set_serialized_transaction(tx_bytes);
4952            relayer_db_for_events(&[relayed_tx.into()], da_height)
4953        }
4954
4955        fn relayer_db_for_events(events: &[Event], da_height: u64) -> Database<Relayer> {
4956            let mut relayer_db = Database::<Relayer>::default();
4957            add_events_to_relayer(&mut relayer_db, da_height.into(), events);
4958            relayer_db
4959        }
4960
4961        #[test]
4962        fn execute_without_commit__relayed_tx_can_spend_message_from_same_da_block() {
4963            let genesis_da_height = 3u64;
4964            let block_height = 1u32;
4965            let da_height = 10u64;
4966            let arb_max_gas = 10_000;
4967
4968            // given
4969            let relayer_db =
4970                relayer_db_with_relayed_tx_spending_message_from_same_da_block(
4971                    da_height,
4972                    arb_max_gas,
4973                );
4974
4975            // when
4976            let on_chain_db = database_with_genesis_block(genesis_da_height);
4977            let producer =
4978                create_relayer_executor(on_chain_db.clone(), relayer_db.clone());
4979            let block = test_block(block_height.into(), da_height.into(), 0);
4980            let (result, _) = producer
4981                .produce_without_commit(block.into())
4982                .unwrap()
4983                .into();
4984
4985            // then
4986            let txs = result.block.transactions();
4987            assert_eq!(txs.len(), 2);
4988
4989            let validator = create_relayer_executor(on_chain_db, relayer_db);
4990            // When
4991            let result = validator.validate(&result.block).map(|_| ());
4992
4993            // Then
4994            assert_eq!(Ok(()), result);
4995        }
4996
4997        fn relayer_db_with_relayed_tx_spending_message_from_same_da_block(
4998            da_height: u64,
4999            max_gas: u64,
5000        ) -> Database<Relayer> {
5001            let mut relayer_db = Database::<Relayer>::default();
5002            let mut message = Message::default();
5003            let nonce = 1.into();
5004            message.set_da_height(da_height.into());
5005            message.set_nonce(nonce);
5006            let message_event = Event::Message(message);
5007
5008            let mut relayed_tx = RelayedTransaction::default();
5009            let tx = TransactionBuilder::script(vec![], vec![])
5010                .script_gas_limit(10)
5011                .add_unsigned_message_input(
5012                    SecretKey::random(&mut StdRng::seed_from_u64(2322)),
5013                    Default::default(),
5014                    nonce,
5015                    Default::default(),
5016                    vec![],
5017                )
5018                .finalize_as_transaction();
5019            let tx_bytes = tx.to_bytes();
5020            relayed_tx.set_serialized_transaction(tx_bytes);
5021            relayed_tx.set_max_gas(max_gas);
5022            let tx_event = Event::Transaction(relayed_tx);
5023            add_events_to_relayer(
5024                &mut relayer_db,
5025                da_height.into(),
5026                &[message_event, tx_event],
5027            );
5028            relayer_db
5029        }
5030
5031        #[test]
5032        fn block_producer_does_not_take_messages_for_the_same_height() {
5033            let genesis_da_height = 1u64;
5034            let on_chain_db = database_with_genesis_block(genesis_da_height);
5035            let mut relayer_db = Database::<Relayer>::default();
5036
5037            // Given
5038            let relayer_da_height = 10u64;
5039            let block_height = 1u32;
5040            let block_da_height = 1u64;
5041            add_messages_to_relayer(&mut relayer_db, relayer_da_height);
5042            assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
5043
5044            // When
5045            let producer = create_relayer_executor(on_chain_db, relayer_db);
5046            let block = test_block(block_height.into(), block_da_height.into(), 10);
5047            let (result, changes) = producer
5048                .produce_without_commit(block.into())
5049                .unwrap()
5050                .into();
5051
5052            // Then
5053            let changes = StorageChanges::Changes(changes);
5054            let view = ChangesIterator::<Column>::new(&changes);
5055            assert!(result.skipped_transactions.is_empty());
5056            assert_eq!(view.iter_all::<Messages>(None).count() as u64, 0);
5057        }
5058
5059        #[test]
5060        fn block_producer_can_use_just_added_message_in_the_transaction() {
5061            let genesis_da_height = 1u64;
5062            let on_chain_db = database_with_genesis_block(genesis_da_height);
5063            let mut relayer_db = Database::<Relayer>::default();
5064
5065            let block_height = 1u32;
5066            let block_da_height = 2u64;
5067            let nonce = 1.into();
5068            let mut message = Message::default();
5069            message.set_da_height(block_da_height.into());
5070            message.set_nonce(nonce);
5071            add_message_to_relayer(&mut relayer_db, message);
5072
5073            // Given
5074            assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
5075            let tx = TransactionBuilder::script(vec![], vec![])
5076                .script_gas_limit(10)
5077                .add_unsigned_message_input(
5078                    SecretKey::random(&mut StdRng::seed_from_u64(2322)),
5079                    Default::default(),
5080                    nonce,
5081                    Default::default(),
5082                    vec![],
5083                )
5084                .finalize_as_transaction();
5085
5086            // When
5087            let mut block = test_block(block_height.into(), block_da_height.into(), 0);
5088            *block.transactions_mut() = vec![tx];
5089            let producer = create_relayer_executor(on_chain_db, relayer_db);
5090            let (result, changes) = producer
5091                .produce_without_commit(block.into())
5092                .unwrap()
5093                .into();
5094
5095            // Then
5096            let changes = StorageChanges::Changes(changes);
5097            let view = ChangesIterator::<Column>::new(&changes);
5098            assert!(result.skipped_transactions.is_empty());
5099            assert_eq!(view.iter_all::<Messages>(None).count() as u64, 0);
5100            assert_eq!(result.events.len(), 2);
5101            assert!(matches!(
5102                result.events[0],
5103                ExecutorEvent::MessageImported(_)
5104            ));
5105            assert!(matches!(
5106                result.events[1],
5107                ExecutorEvent::MessageConsumed(_)
5108            ));
5109        }
5110    }
5111}