fuel_core_executor/
executor.rs

1use crate::{
2    ports::{
3        MaybeCheckedTransaction,
4        NewTxWaiterPort,
5        PreconfirmationSenderPort,
6        RelayerPort,
7        TransactionsSource,
8    },
9    refs::ContractRef,
10};
11use fuel_core_storage::{
12    StorageAsMut,
13    StorageAsRef,
14    column::Column,
15    kv_store::KeyValueInspect,
16    tables::{
17        Coins,
18        ConsensusParametersVersions,
19        ContractsLatestUtxo,
20        FuelBlocks,
21        Messages,
22        ProcessedTransactions,
23    },
24    transactional::{
25        Changes,
26        ConflictPolicy,
27        IntoTransaction,
28        Modifiable,
29        ReadTransaction,
30        StorageTransaction,
31        WriteTransaction,
32    },
33    vm_storage::VmStorage,
34};
35use fuel_core_types::{
36    blockchain::{
37        block::{
38            Block,
39            PartialFuelBlock,
40        },
41        header::{
42            ConsensusParametersVersion,
43            PartialBlockHeader,
44        },
45        primitives::DaBlockHeight,
46        transaction::TransactionExt,
47    },
48    entities::{
49        RelayedTransaction,
50        coins::coin::{
51            CompressedCoin,
52            CompressedCoinV1,
53        },
54        contract::ContractUtxoInfo,
55    },
56    fuel_asm::{
57        PanicInstruction,
58        Word,
59        op,
60    },
61    fuel_merkle::binary::root_calculator::MerkleRootCalculator,
62    fuel_tx::{
63        Address,
64        AssetId,
65        Bytes32,
66        Cacheable,
67        Chargeable,
68        ConsensusParameters,
69        Input,
70        Mint,
71        Output,
72        PanicReason,
73        Receipt,
74        Transaction,
75        TxId,
76        TxPointer,
77        UniqueIdentifier,
78        UtxoId,
79        field::{
80            InputContract,
81            MaxFeeLimit,
82            MintAmount,
83            MintAssetId,
84            MintGasPrice,
85            OutputContract,
86            TxPointer as TxPointerField,
87        },
88        input::{
89            self,
90            coin::{
91                CoinPredicate,
92                CoinSigned,
93            },
94            message::{
95                MessageCoinPredicate,
96                MessageCoinSigned,
97                MessageDataPredicate,
98                MessageDataSigned,
99            },
100        },
101        output,
102    },
103    fuel_types::{
104        BlockHeight,
105        ContractId,
106        MessageId,
107        canonical::Deserialize,
108    },
109    fuel_vm::{
110        self,
111        Interpreter,
112        ProgramState,
113        checked_transaction::{
114            CheckPredicateParams,
115            CheckPredicates,
116            Checked,
117            CheckedTransaction,
118            Checks,
119            IntoChecked,
120        },
121        interpreter::{
122            CheckedMetadata as CheckedMetadataTrait,
123            ExecutableTransaction,
124            InterpreterParams,
125            MemoryInstance,
126            NotSupportedEcal,
127        },
128        state::StateTransition,
129        verification,
130    },
131    services::{
132        block_producer::Components,
133        executor::{
134            Error as ExecutorError,
135            Event as ExecutorEvent,
136            ExecutionResult,
137            ForcedTransactionFailure,
138            Result as ExecutorResult,
139            TransactionExecutionResult,
140            TransactionExecutionStatus,
141            TransactionValidityError,
142            UncommittedResult,
143            UncommittedValidationResult,
144            ValidationResult,
145        },
146        preconfirmation::{
147            Preconfirmation,
148            PreconfirmationStatus,
149        },
150        relayer::Event,
151    },
152};
153use parking_lot::Mutex as ParkingMutex;
154use tracing::{
155    debug,
156    warn,
157};
158
159#[cfg(feature = "std")]
160use std::borrow::Cow;
161
162#[cfg(not(feature = "std"))]
163use alloc::borrow::Cow;
164
165#[cfg(feature = "alloc")]
166use alloc::{
167    format,
168    string::ToString,
169    vec,
170    vec::Vec,
171};
172
173/// The maximum amount of transactions that can be included in a block,
174/// excluding the mint transaction.
175#[cfg(not(feature = "limited-tx-count"))]
176pub const fn max_tx_count() -> u16 {
177    u16::MAX.saturating_sub(1)
178}
179#[cfg(feature = "limited-tx-count")]
180pub const fn max_tx_count() -> u16 {
181    1024
182}
183
184pub struct OnceTransactionsSource {
185    transactions: ParkingMutex<Vec<MaybeCheckedTransaction>>,
186}
187
188impl OnceTransactionsSource {
189    pub fn new(transactions: Vec<Transaction>) -> Self {
190        Self {
191            transactions: ParkingMutex::new(
192                transactions
193                    .into_iter()
194                    .map(MaybeCheckedTransaction::Transaction)
195                    .collect(),
196            ),
197        }
198    }
199
200    pub fn new_maybe_checked(transactions: Vec<MaybeCheckedTransaction>) -> Self {
201        Self {
202            transactions: ParkingMutex::new(transactions),
203        }
204    }
205}
206
207impl TransactionsSource for OnceTransactionsSource {
208    fn next(
209        &self,
210        _: u64,
211        transactions_limit: u16,
212        _: u32,
213    ) -> Vec<MaybeCheckedTransaction> {
214        let mut lock = self.transactions.lock();
215        let transactions: &mut Vec<MaybeCheckedTransaction> = lock.as_mut();
216        // Avoid panicking if we request more transactions than there are in the vector
217        let transactions_limit = (transactions_limit as usize).min(transactions.len());
218        transactions.drain(..transactions_limit).collect()
219    }
220}
221
222/// The result of waiting for new transactions.
223pub enum WaitNewTransactionsResult {
224    /// We received a new transaction and we can continue the block execution.
225    NewTransaction,
226    /// We didn't receive any new transaction and we can end the block execution.
227    Timeout,
228}
229
230pub struct TimeoutOnlyTxWaiter;
231
232impl NewTxWaiterPort for TimeoutOnlyTxWaiter {
233    async fn wait_for_new_transactions(&mut self) -> WaitNewTransactionsResult {
234        WaitNewTransactionsResult::Timeout
235    }
236}
237
238pub struct TransparentPreconfirmationSender;
239
240impl PreconfirmationSenderPort for TransparentPreconfirmationSender {
241    fn try_send(&self, _: Vec<Preconfirmation>) -> Vec<Preconfirmation> {
242        vec![]
243    }
244
245    async fn send(&self, _: Vec<Preconfirmation>) {}
246}
247
248pub fn convert_tx_execution_result_to_preconfirmation(
249    tx: &Transaction,
250    tx_id: TxId,
251    tx_exec_result: &TransactionExecutionResult,
252    block_height: BlockHeight,
253    tx_index: u16,
254) -> Preconfirmation {
255    let tx_pointer = TxPointer::new(block_height, tx_index);
256    let dynamic_outputs = tx
257        .outputs()
258        .iter()
259        .enumerate()
260        .filter_map(|(i, output)| {
261            if output.is_change() || output.is_variable() && output.amount() != Some(0) {
262                let output_index = u16::try_from(i).ok()?;
263                let utxo_id = UtxoId::new(tx_id, output_index);
264                Some((utxo_id, *output))
265            } else {
266                None
267            }
268        })
269        .collect();
270
271    let status = match tx_exec_result {
272        TransactionExecutionResult::Success {
273            receipts,
274            total_gas,
275            total_fee,
276            ..
277        } => PreconfirmationStatus::Success {
278            total_gas: *total_gas,
279            total_fee: *total_fee,
280            tx_pointer,
281            receipts: receipts.clone(),
282            outputs: dynamic_outputs,
283        },
284        TransactionExecutionResult::Failed {
285            receipts,
286            total_gas,
287            total_fee,
288            ..
289        } => PreconfirmationStatus::Failure {
290            total_gas: *total_gas,
291            total_fee: *total_fee,
292            tx_pointer,
293            receipts: receipts.clone(),
294            outputs: dynamic_outputs,
295        },
296    };
297    Preconfirmation { tx_id, status }
298}
299
300/// Data that is generated after executing all transactions.
301#[derive(Default)]
302pub struct ExecutionData {
303    coinbase: u64,
304    used_gas: u64,
305    used_size: u32,
306    tx_count: u16,
307    found_mint: bool,
308    message_ids: Vec<MessageId>,
309    tx_status: Vec<TransactionExecutionStatus>,
310    events: Vec<ExecutorEvent>,
311    changes: Changes,
312    pub skipped_transactions: Vec<(TxId, ExecutorError)>,
313    event_inbox_root: Bytes32,
314}
315
316impl ExecutionData {
317    pub fn new() -> Self {
318        ExecutionData {
319            coinbase: 0,
320            used_gas: 0,
321            used_size: 0,
322            tx_count: 0,
323            found_mint: false,
324            message_ids: Vec::new(),
325            tx_status: Vec::new(),
326            events: Vec::new(),
327            changes: Default::default(),
328            skipped_transactions: Vec::new(),
329            event_inbox_root: Default::default(),
330        }
331    }
332}
333
334/// Per-block execution options.
335/// These are passed to the executor.
336#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]
337pub struct ExecutionOptions {
338    /// The flag allows the usage of fake coins in the inputs of the transaction.
339    /// When `false` the executor skips signature and UTXO existence checks.
340    pub forbid_fake_coins: bool,
341    /// Print execution backtraces if transaction execution reverts.
342    ///
343    /// Deprecated field. Do nothing. This fields exists for serialization and
344    /// deserialization compatibility.
345    pub backtrace: bool,
346}
347
348/// Per-block execution options
349#[derive(Clone, Default, Debug)]
350struct ExecutionOptionsInner {
351    /// The flag allows the usage of fake coins in the inputs of the transaction.
352    /// When `false` the executor skips signature and UTXO existence checks.
353    pub forbid_fake_coins: bool,
354    pub dry_run: bool,
355}
356
357/// The executor instance performs block production and validation. Given a block, it will execute all
358/// the transactions contained in the block and persist changes to the underlying database as needed.
359#[derive(Clone, Debug)]
360pub struct ExecutionInstance<R, D> {
361    pub relayer: R,
362    pub database: D,
363    pub options: ExecutionOptions,
364}
365
366impl<R, D> ExecutionInstance<R, D>
367where
368    R: RelayerPort,
369    D: KeyValueInspect<Column = Column>,
370{
371    pub fn new(relayer: R, database: D, options: ExecutionOptions) -> Self {
372        Self {
373            relayer,
374            database,
375            options,
376        }
377    }
378
379    #[tracing::instrument(skip_all)]
380    pub async fn produce_without_commit<TxSource>(
381        self,
382        components: Components<TxSource>,
383        dry_run: bool,
384        new_tx_waiter: impl NewTxWaiterPort,
385        preconfirmation_sender: impl PreconfirmationSenderPort,
386    ) -> ExecutorResult<UncommittedResult<Changes>>
387    where
388        TxSource: TransactionsSource,
389    {
390        let consensus_params_version = components.consensus_parameters_version();
391
392        let (block_executor, storage_tx) = self.into_executor(
393            consensus_params_version,
394            new_tx_waiter,
395            preconfirmation_sender,
396            dry_run,
397        )?;
398
399        #[cfg(feature = "fault-proving")]
400        let chain_id = block_executor.consensus_params.chain_id();
401
402        let (partial_block, execution_data) =
403            block_executor.execute(components, storage_tx).await?;
404
405        let ExecutionData {
406            message_ids,
407            event_inbox_root,
408            changes,
409            events,
410            tx_status,
411            skipped_transactions,
412            coinbase,
413            used_gas,
414            used_size,
415            ..
416        } = execution_data;
417
418        let block = partial_block
419            .generate(
420                &message_ids[..],
421                event_inbox_root,
422                #[cfg(feature = "fault-proving")]
423                &chain_id,
424            )
425            .map_err(ExecutorError::BlockHeaderError)?;
426
427        let finalized_block_id = block.id();
428
429        debug!(
430            "Block {:#x} fees: {} gas: {} tx_size: {}",
431            finalized_block_id, coinbase, used_gas, used_size
432        );
433
434        let result = ExecutionResult {
435            block,
436            skipped_transactions,
437            tx_status,
438            events,
439        };
440
441        Ok(UncommittedResult::new(result, changes))
442    }
443
444    pub fn validate_without_commit(
445        self,
446        block: &Block,
447    ) -> ExecutorResult<UncommittedValidationResult<Changes>> {
448        let consensus_params_version = block.header().consensus_parameters_version();
449        let (block_executor, storage_tx) = self.into_executor(
450            consensus_params_version,
451            TimeoutOnlyTxWaiter,
452            TransparentPreconfirmationSender,
453            false,
454        )?;
455
456        let ExecutionData {
457            coinbase,
458            used_gas,
459            used_size,
460            tx_status,
461            events,
462            changes,
463            ..
464        } = block_executor.validate_block(block, storage_tx)?;
465
466        let finalized_block_id = block.id();
467
468        debug!(
469            "Block {:#x} fees: {} gas: {} tx_size: {}",
470            finalized_block_id, coinbase, used_gas, used_size
471        );
472
473        let result = ValidationResult { tx_status, events };
474
475        Ok(UncommittedValidationResult::new(result, changes))
476    }
477
478    fn into_executor<N, P>(
479        self,
480        consensus_params_version: ConsensusParametersVersion,
481        new_tx_waiter: N,
482        preconfirmation_sender: P,
483        dry_run: bool,
484    ) -> ExecutorResult<(BlockExecutor<R, N, P>, StorageTransaction<D>)> {
485        let storage_tx = self
486            .database
487            .into_transaction()
488            .with_policy(ConflictPolicy::Overwrite);
489        let consensus_params = storage_tx
490            .storage::<ConsensusParametersVersions>()
491            .get(&consensus_params_version)?
492            .ok_or(ExecutorError::ConsensusParametersNotFound(
493                consensus_params_version,
494            ))?
495            .into_owned();
496        let executor = BlockExecutor::new(
497            self.relayer,
498            self.options,
499            consensus_params,
500            new_tx_waiter,
501            preconfirmation_sender,
502            dry_run,
503        )?;
504        Ok((executor, storage_tx))
505    }
506}
507
508type BlockStorageTransaction<T> = StorageTransaction<T>;
509type TxStorageTransaction<'a, T> = StorageTransaction<&'a mut BlockStorageTransaction<T>>;
510
511#[derive(Clone, Debug)]
512pub struct BlockExecutor<R, TxWaiter, PreconfirmationSender> {
513    relayer: R,
514    consensus_params: ConsensusParameters,
515    options: ExecutionOptionsInner,
516    new_tx_waiter: TxWaiter,
517    preconfirmation_sender: PreconfirmationSender,
518}
519
520impl<R, TxWaiter, PreconfirmationSender>
521    BlockExecutor<R, TxWaiter, PreconfirmationSender>
522{
523    pub fn new(
524        relayer: R,
525        options: ExecutionOptions,
526        consensus_params: ConsensusParameters,
527        new_tx_waiter: TxWaiter,
528        preconfirmation_sender: PreconfirmationSender,
529        dry_run: bool,
530    ) -> ExecutorResult<Self> {
531        Ok(Self {
532            relayer,
533            consensus_params,
534            options: ExecutionOptionsInner {
535                forbid_fake_coins: options.forbid_fake_coins,
536                dry_run,
537            },
538            new_tx_waiter,
539            preconfirmation_sender,
540        })
541    }
542}
543
544impl<R, N, P> BlockExecutor<R, N, P>
545where
546    R: RelayerPort,
547    N: NewTxWaiterPort,
548    P: PreconfirmationSenderPort,
549{
550    async fn execute<TxSource, D>(
551        self,
552        components: Components<TxSource>,
553        block_storage_tx: BlockStorageTransaction<D>,
554    ) -> ExecutorResult<(PartialFuelBlock, ExecutionData)>
555    where
556        TxSource: TransactionsSource,
557        D: KeyValueInspect<Column = Column>,
558    {
559        if self.options.dry_run {
560            self.dry_run_block(components, block_storage_tx).await
561        } else {
562            self.produce_block(components, block_storage_tx).await
563        }
564    }
565
566    #[tracing::instrument(skip_all)]
567    /// Produce the fuel block with specified components
568    async fn produce_block<TxSource, D>(
569        mut self,
570        components: Components<TxSource>,
571        mut block_storage_tx: BlockStorageTransaction<D>,
572    ) -> ExecutorResult<(PartialFuelBlock, ExecutionData)>
573    where
574        TxSource: TransactionsSource,
575        D: KeyValueInspect<Column = Column>,
576    {
577        let mut partial_block =
578            PartialFuelBlock::new(components.header_to_produce, vec![]);
579        let mut data = ExecutionData::new();
580        let mut memory = MemoryInstance::new();
581
582        self.process_l1_txs(
583            &mut partial_block,
584            components.coinbase_recipient,
585            &mut block_storage_tx,
586            &mut data,
587            &mut memory,
588        )?;
589
590        loop {
591            self.process_l2_txs(
592                &mut partial_block,
593                &components,
594                &mut block_storage_tx,
595                &mut data,
596                &mut memory,
597            )
598            .await?;
599            match self.new_tx_waiter.wait_for_new_transactions().await {
600                WaitNewTransactionsResult::Timeout => break,
601                WaitNewTransactionsResult::NewTransaction => {
602                    continue;
603                }
604            }
605        }
606
607        self.produce_mint_tx(
608            &mut partial_block,
609            &components,
610            &mut block_storage_tx,
611            &mut data,
612            &mut memory,
613        )?;
614        debug_assert!(data.found_mint, "Mint transaction is not found");
615
616        data.changes = block_storage_tx.into_changes();
617        Ok((partial_block, data))
618    }
619
620    fn produce_mint_tx<TxSource, T>(
621        &self,
622        block: &mut PartialFuelBlock,
623        components: &Components<TxSource>,
624        storage_tx: &mut BlockStorageTransaction<T>,
625        data: &mut ExecutionData,
626        memory: &mut MemoryInstance,
627    ) -> ExecutorResult<()>
628    where
629        T: KeyValueInspect<Column = Column>,
630    {
631        let Components {
632            coinbase_recipient: coinbase_contract_id,
633            gas_price,
634            ..
635        } = *components;
636
637        let amount_to_mint = if coinbase_contract_id != ContractId::zeroed() {
638            data.coinbase
639        } else {
640            0
641        };
642        let block_height = *block.header.height();
643
644        let coinbase_tx = Transaction::mint(
645            TxPointer::new(block_height, data.tx_count),
646            input::contract::Contract {
647                utxo_id: UtxoId::new(Bytes32::zeroed(), 0),
648                balance_root: Bytes32::zeroed(),
649                state_root: Bytes32::zeroed(),
650                tx_pointer: TxPointer::new(BlockHeight::new(0), 0),
651                contract_id: coinbase_contract_id,
652            },
653            output::contract::Contract {
654                input_index: 0,
655                balance_root: Bytes32::zeroed(),
656                state_root: Bytes32::zeroed(),
657            },
658            amount_to_mint,
659            *self.consensus_params.base_asset_id(),
660            gas_price,
661        );
662
663        self.execute_transaction_and_commit(
664            block,
665            storage_tx,
666            data,
667            MaybeCheckedTransaction::Transaction(coinbase_tx.into()),
668            gas_price,
669            coinbase_contract_id,
670            memory,
671        )?;
672
673        Ok(())
674    }
675
676    /// Execute dry-run of block with specified components
677    async fn dry_run_block<TxSource, D>(
678        mut self,
679        components: Components<TxSource>,
680        mut block_storage_tx: BlockStorageTransaction<D>,
681    ) -> ExecutorResult<(PartialFuelBlock, ExecutionData)>
682    where
683        TxSource: TransactionsSource,
684        D: KeyValueInspect<Column = Column>,
685    {
686        let mut partial_block =
687            PartialFuelBlock::new(components.header_to_produce, vec![]);
688        let mut data = ExecutionData::new();
689
690        self.process_l2_txs(
691            &mut partial_block,
692            &components,
693            &mut block_storage_tx,
694            &mut data,
695            &mut MemoryInstance::new(),
696        )
697        .await?;
698
699        data.changes = block_storage_tx.into_changes();
700        Ok((partial_block, data))
701    }
702
703    fn process_l1_txs<T>(
704        &mut self,
705        block: &mut PartialFuelBlock,
706        coinbase_contract_id: ContractId,
707        storage_tx: &mut BlockStorageTransaction<T>,
708        data: &mut ExecutionData,
709        memory: &mut MemoryInstance,
710    ) -> ExecutorResult<()>
711    where
712        T: KeyValueInspect<Column = Column>,
713    {
714        let block_height = *block.header.height();
715        let da_block_height = block.header.da_height;
716        let relayed_txs = self.get_relayed_txs(
717            block_height,
718            da_block_height,
719            data,
720            storage_tx,
721            memory,
722        )?;
723
724        self.process_relayed_txs(
725            relayed_txs,
726            block,
727            storage_tx,
728            data,
729            coinbase_contract_id,
730            memory,
731        )?;
732
733        Ok(())
734    }
735
736    async fn process_l2_txs<T, TxSource>(
737        &mut self,
738        block: &mut PartialFuelBlock,
739        components: &Components<TxSource>,
740        storage_tx: &mut StorageTransaction<T>,
741        data: &mut ExecutionData,
742        memory: &mut MemoryInstance,
743    ) -> ExecutorResult<()>
744    where
745        T: KeyValueInspect<Column = Column>,
746        TxSource: TransactionsSource,
747    {
748        let Components {
749            transactions_source: l2_tx_source,
750            coinbase_recipient: coinbase_contract_id,
751            gas_price,
752            ..
753        } = components;
754        let block_gas_limit = self.consensus_params.block_gas_limit();
755        let block_transaction_size_limit = self
756            .consensus_params
757            .block_transaction_size_limit()
758            .try_into()
759            .unwrap_or(u32::MAX);
760
761        let mut remaining_gas_limit = block_gas_limit.saturating_sub(data.used_gas);
762        let mut remaining_block_transaction_size_limit =
763            block_transaction_size_limit.saturating_sub(data.used_size);
764
765        // We allow at most u16::MAX transactions in a block, including the mint transaction.
766        // When processing l2 transactions, we must take into account transactions from the l1
767        // that have been included in the block already (stored in `data.tx_count`), as well
768        // as the final mint transaction.
769        let mut remaining_tx_count = max_tx_count().saturating_sub(data.tx_count);
770
771        let mut regular_tx_iter = l2_tx_source
772            .next(
773                remaining_gas_limit,
774                remaining_tx_count,
775                remaining_block_transaction_size_limit,
776            )
777            .into_iter()
778            .take(remaining_tx_count as usize)
779            .peekable();
780        let mut statuses = vec![];
781        while regular_tx_iter.peek().is_some() {
782            for transaction in regular_tx_iter {
783                let tx_id = transaction.id(&self.consensus_params.chain_id());
784                let tx_max_gas = transaction.max_gas(&self.consensus_params)?;
785                if tx_max_gas > remaining_gas_limit {
786                    data.skipped_transactions.push((
787                        tx_id,
788                        ExecutorError::GasOverflow(
789                            format!("Transaction cannot fit in remaining gas limit: ({remaining_gas_limit})."),
790                            tx_max_gas,
791                            remaining_gas_limit,
792                        ),
793                    ));
794                    continue;
795                }
796                match self.execute_transaction_and_commit(
797                    block,
798                    storage_tx,
799                    data,
800                    transaction,
801                    *gas_price,
802                    *coinbase_contract_id,
803                    memory,
804                ) {
805                    Ok(_) => {
806                        // SAFETY: Transaction succeed so we know that `tx_status` has been populated
807                        let latest_executed_tx = data.tx_status.last().expect(
808                            "Shouldn't happens as we just added a transaction; qed",
809                        );
810                        let tx = block.transactions.last().expect(
811                            "Shouldn't happens as we just added a transaction; qed",
812                        );
813                        let preconfirmation_status =
814                            convert_tx_execution_result_to_preconfirmation(
815                                tx,
816                                tx_id,
817                                &latest_executed_tx.result,
818                                *block.header.height(),
819                                data.tx_count,
820                            );
821                        statuses.push(preconfirmation_status);
822                    }
823                    Err(err) => {
824                        statuses.push(Preconfirmation {
825                            tx_id,
826                            status: PreconfirmationStatus::SqueezedOut {
827                                reason: err.to_string(),
828                            },
829                        });
830                        data.skipped_transactions.push((tx_id, err));
831                    }
832                }
833                statuses = self.preconfirmation_sender.try_send(statuses);
834                remaining_gas_limit = block_gas_limit.saturating_sub(data.used_gas);
835                remaining_block_transaction_size_limit =
836                    block_transaction_size_limit.saturating_sub(data.used_size);
837                remaining_tx_count = max_tx_count().saturating_sub(data.tx_count);
838            }
839
840            regular_tx_iter = l2_tx_source
841                .next(
842                    remaining_gas_limit,
843                    remaining_tx_count,
844                    remaining_block_transaction_size_limit,
845                )
846                .into_iter()
847                .take(remaining_tx_count as usize)
848                .peekable();
849        }
850        if !statuses.is_empty() {
851            self.preconfirmation_sender.send(statuses).await;
852        }
853
854        Ok(())
855    }
856
857    #[allow(clippy::too_many_arguments)]
858    fn execute_transaction_and_commit<'a, W>(
859        &'a self,
860        block: &'a mut PartialFuelBlock,
861        storage_tx: &mut BlockStorageTransaction<W>,
862        execution_data: &mut ExecutionData,
863        tx: MaybeCheckedTransaction,
864        gas_price: Word,
865        coinbase_contract_id: ContractId,
866        memory: &mut MemoryInstance,
867    ) -> ExecutorResult<()>
868    where
869        W: KeyValueInspect<Column = Column>,
870    {
871        let tx_count = execution_data.tx_count;
872        let tx = {
873            let mut tx_st_transaction = storage_tx
874                .write_transaction()
875                .with_policy(ConflictPolicy::Overwrite);
876            let tx_id = tx.id(&self.consensus_params.chain_id());
877            let tx = self.execute_transaction(
878                tx,
879                &tx_id,
880                &block.header,
881                coinbase_contract_id,
882                gas_price,
883                execution_data,
884                &mut tx_st_transaction,
885                memory,
886            )?;
887            tx_st_transaction.commit()?;
888            tx
889        };
890
891        block.transactions.push(tx);
892        execution_data.tx_count = tx_count
893            .checked_add(1)
894            .ok_or(ExecutorError::TooManyTransactions)?;
895
896        Ok(())
897    }
898
899    #[tracing::instrument(skip_all)]
900    fn validate_block<D>(
901        mut self,
902        block: &Block,
903        mut block_storage_tx: StorageTransaction<D>,
904    ) -> ExecutorResult<ExecutionData>
905    where
906        D: KeyValueInspect<Column = Column>,
907    {
908        let mut data = ExecutionData::new();
909
910        let partial_header = PartialBlockHeader::from(block.header());
911        let mut partial_block = PartialFuelBlock::new(partial_header, vec![]);
912        let transactions = block.transactions();
913        let mut memory = MemoryInstance::new();
914
915        let (gas_price, coinbase_contract_id) =
916            Self::get_coinbase_info_from_mint_tx(transactions)?;
917
918        self.process_l1_txs(
919            &mut partial_block,
920            coinbase_contract_id,
921            &mut block_storage_tx,
922            &mut data,
923            &mut memory,
924        )?;
925        let processed_l1_tx_count = partial_block.transactions.len();
926
927        for transaction in transactions.iter().skip(processed_l1_tx_count) {
928            let maybe_checked_tx =
929                MaybeCheckedTransaction::Transaction(transaction.clone());
930            self.execute_transaction_and_commit(
931                &mut partial_block,
932                &mut block_storage_tx,
933                &mut data,
934                maybe_checked_tx,
935                gas_price,
936                coinbase_contract_id,
937                &mut memory,
938            )?;
939        }
940
941        self.check_block_matches(partial_block, block, &data)?;
942
943        data.changes = block_storage_tx.into_changes();
944        Ok(data)
945    }
946
947    fn get_coinbase_info_from_mint_tx(
948        transactions: &[Transaction],
949    ) -> ExecutorResult<(u64, ContractId)> {
950        if let Some(Transaction::Mint(mint)) = transactions.last() {
951            Ok((*mint.gas_price(), mint.input_contract().contract_id))
952        } else {
953            Err(ExecutorError::MintMissing)
954        }
955    }
956
957    const RELAYED_GAS_PRICE: Word = 0;
958
959    fn process_relayed_txs<T>(
960        &self,
961        forced_transactions: Vec<CheckedTransaction>,
962        partial_block: &mut PartialFuelBlock,
963        storage_tx: &mut BlockStorageTransaction<T>,
964        data: &mut ExecutionData,
965        coinbase_contract_id: ContractId,
966        memory: &mut MemoryInstance,
967    ) -> ExecutorResult<()>
968    where
969        T: KeyValueInspect<Column = Column>,
970    {
971        let block_header = partial_block.header;
972        let block_height = block_header.height();
973        let consensus_parameters_version = block_header.consensus_parameters_version;
974        let relayed_tx_iter = forced_transactions.into_iter();
975        for checked in relayed_tx_iter {
976            let maybe_checked_transaction = MaybeCheckedTransaction::CheckedTransaction(
977                checked,
978                consensus_parameters_version,
979            );
980            let tx_id = maybe_checked_transaction.id(&self.consensus_params.chain_id());
981            match self.execute_transaction_and_commit(
982                partial_block,
983                storage_tx,
984                data,
985                maybe_checked_transaction,
986                Self::RELAYED_GAS_PRICE,
987                coinbase_contract_id,
988                memory,
989            ) {
990                Ok(_) => {}
991                Err(err) => {
992                    let event = ExecutorEvent::ForcedTransactionFailed {
993                        id: tx_id.into(),
994                        block_height: *block_height,
995                        failure: err.to_string(),
996                    };
997                    data.events.push(event);
998                }
999            }
1000        }
1001        Ok(())
1002    }
1003
1004    fn get_relayed_txs<D>(
1005        &mut self,
1006        block_height: BlockHeight,
1007        da_block_height: DaBlockHeight,
1008        data: &mut ExecutionData,
1009        block_storage_tx: &mut BlockStorageTransaction<D>,
1010        memory: &mut MemoryInstance,
1011    ) -> ExecutorResult<Vec<CheckedTransaction>>
1012    where
1013        D: KeyValueInspect<Column = Column>,
1014    {
1015        let forced_transactions = if self.relayer.enabled() {
1016            self.process_da(
1017                block_height,
1018                da_block_height,
1019                data,
1020                block_storage_tx,
1021                memory,
1022            )?
1023        } else {
1024            Vec::with_capacity(0)
1025        };
1026        Ok(forced_transactions)
1027    }
1028
1029    fn check_block_matches(
1030        &self,
1031        new_partial_block: PartialFuelBlock,
1032        old_block: &Block,
1033        data: &ExecutionData,
1034    ) -> ExecutorResult<()> {
1035        let ExecutionData {
1036            message_ids,
1037            event_inbox_root,
1038            ..
1039        } = &data;
1040
1041        let chain_id = self.consensus_params.chain_id();
1042
1043        new_partial_block
1044            .transactions
1045            .iter()
1046            .zip(old_block.transactions())
1047            .try_for_each(|(new_tx, old_tx)| {
1048                if new_tx != old_tx {
1049                    let transaction_id = old_tx.id(&chain_id);
1050
1051                    tracing::info!(
1052                        "Transaction {:?} does not match: new_tx {:?} and old_tx {:?}",
1053                        transaction_id,
1054                        new_tx,
1055                        old_tx
1056                    );
1057
1058                    Err(ExecutorError::InvalidTransactionOutcome { transaction_id })
1059                } else {
1060                    Ok(())
1061                }
1062            })?;
1063
1064        let new_block = new_partial_block
1065            .generate(
1066                &message_ids[..],
1067                *event_inbox_root,
1068                #[cfg(feature = "fault-proving")]
1069                &chain_id,
1070            )
1071            .map_err(ExecutorError::BlockHeaderError)?;
1072        if new_block.header() != old_block.header() {
1073            tracing::info!(
1074                "Headers does not match: new_block {:?} and old_block {:?}",
1075                new_block,
1076                old_block
1077            );
1078
1079            Err(ExecutorError::BlockMismatch)
1080        } else {
1081            Ok(())
1082        }
1083    }
1084
1085    fn process_da<D>(
1086        &mut self,
1087        block_height: BlockHeight,
1088        da_block_height: DaBlockHeight,
1089        execution_data: &mut ExecutionData,
1090        block_storage_tx: &mut BlockStorageTransaction<D>,
1091        memory: &mut MemoryInstance,
1092    ) -> ExecutorResult<Vec<CheckedTransaction>>
1093    where
1094        D: KeyValueInspect<Column = Column>,
1095    {
1096        let prev_block_height = block_height
1097            .pred()
1098            .ok_or(ExecutorError::ExecutingGenesisBlock)?;
1099
1100        let prev_block_header = block_storage_tx
1101            .storage::<FuelBlocks>()
1102            .get(&prev_block_height)?
1103            .ok_or(ExecutorError::PreviousBlockIsNotFound)?;
1104        let previous_da_height = prev_block_header.header().da_height();
1105        let Some(next_unprocessed_da_height) = previous_da_height.0.checked_add(1) else {
1106            return Err(ExecutorError::DaHeightExceededItsLimit)
1107        };
1108
1109        let mut root_calculator = MerkleRootCalculator::new();
1110
1111        let mut checked_forced_txs = vec![];
1112
1113        for da_height in next_unprocessed_da_height..=da_block_height.0 {
1114            let da_height = da_height.into();
1115            let events = self
1116                .relayer
1117                .get_events(&da_height)
1118                .map_err(|err| ExecutorError::RelayerError(err.to_string()))?;
1119            for event in events {
1120                root_calculator.push(event.hash().as_ref());
1121                match event {
1122                    Event::Message(message) => {
1123                        if message.da_height() != da_height {
1124                            return Err(ExecutorError::RelayerGivesIncorrectMessages)
1125                        }
1126                        block_storage_tx
1127                            .storage_as_mut::<Messages>()
1128                            .insert(message.nonce(), &message)?;
1129                        execution_data
1130                            .events
1131                            .push(ExecutorEvent::MessageImported(message));
1132                    }
1133                    Event::Transaction(relayed_tx) => {
1134                        let id = relayed_tx.id();
1135                        let checked_tx_res = Self::validate_forced_tx(
1136                            relayed_tx,
1137                            block_height,
1138                            &self.consensus_params,
1139                            memory,
1140                            block_storage_tx,
1141                        );
1142                        match checked_tx_res {
1143                            Ok(checked_tx) => {
1144                                checked_forced_txs.push(checked_tx);
1145                            }
1146                            Err(err) => {
1147                                execution_data.events.push(
1148                                    ExecutorEvent::ForcedTransactionFailed {
1149                                        id,
1150                                        block_height,
1151                                        failure: err.to_string(),
1152                                    },
1153                                );
1154                            }
1155                        }
1156                    }
1157                }
1158            }
1159        }
1160
1161        execution_data.event_inbox_root = root_calculator.root().into();
1162
1163        Ok(checked_forced_txs)
1164    }
1165
1166    /// Parse forced transaction payloads and perform basic checks
1167    fn validate_forced_tx<D>(
1168        relayed_tx: RelayedTransaction,
1169        block_height: BlockHeight,
1170        consensus_params: &ConsensusParameters,
1171        memory: &mut MemoryInstance,
1172        block_storage_tx: &BlockStorageTransaction<D>,
1173    ) -> Result<CheckedTransaction, ForcedTransactionFailure>
1174    where
1175        D: KeyValueInspect<Column = Column>,
1176    {
1177        let parsed_tx = Self::parse_tx_bytes(&relayed_tx)?;
1178        Self::tx_is_valid_variant(&parsed_tx)?;
1179        Self::relayed_tx_claimed_enough_max_gas(
1180            &parsed_tx,
1181            &relayed_tx,
1182            consensus_params,
1183        )?;
1184        let checked_tx = Self::get_checked_tx(
1185            parsed_tx,
1186            block_height,
1187            consensus_params,
1188            memory,
1189            block_storage_tx,
1190        )?;
1191        Ok(CheckedTransaction::from(checked_tx))
1192    }
1193
1194    fn parse_tx_bytes(
1195        relayed_transaction: &RelayedTransaction,
1196    ) -> Result<Transaction, ForcedTransactionFailure> {
1197        let tx_bytes = relayed_transaction.serialized_transaction();
1198        let tx = Transaction::from_bytes(tx_bytes)
1199            .map_err(|_| ForcedTransactionFailure::CodecError)?;
1200        Ok(tx)
1201    }
1202
1203    fn get_checked_tx<D>(
1204        tx: Transaction,
1205        height: BlockHeight,
1206        consensus_params: &ConsensusParameters,
1207        memory: &mut MemoryInstance,
1208        block_storage_tx: &BlockStorageTransaction<D>,
1209    ) -> Result<Checked<Transaction>, ForcedTransactionFailure>
1210    where
1211        D: KeyValueInspect<Column = Column>,
1212    {
1213        let checked_tx = tx
1214            .into_checked_reusable_memory(
1215                height,
1216                consensus_params,
1217                memory,
1218                block_storage_tx,
1219            )
1220            .map_err(ForcedTransactionFailure::CheckError)?;
1221        Ok(checked_tx)
1222    }
1223
1224    fn tx_is_valid_variant(tx: &Transaction) -> Result<(), ForcedTransactionFailure> {
1225        match tx {
1226            Transaction::Mint(_) => Err(ForcedTransactionFailure::InvalidTransactionType),
1227            Transaction::Script(_)
1228            | Transaction::Create(_)
1229            | Transaction::Upgrade(_)
1230            | Transaction::Upload(_)
1231            | Transaction::Blob(_) => Ok(()),
1232        }
1233    }
1234
1235    fn relayed_tx_claimed_enough_max_gas(
1236        tx: &Transaction,
1237        relayed_tx: &RelayedTransaction,
1238        consensus_params: &ConsensusParameters,
1239    ) -> Result<(), ForcedTransactionFailure> {
1240        let claimed_max_gas = relayed_tx.max_gas();
1241        let actual_max_gas = tx
1242            .max_gas(consensus_params)
1243            .map_err(|_| ForcedTransactionFailure::InvalidTransactionType)?;
1244        if actual_max_gas > claimed_max_gas {
1245            return Err(ForcedTransactionFailure::InsufficientMaxGas {
1246                claimed_max_gas,
1247                actual_max_gas,
1248            });
1249        }
1250        Ok(())
1251    }
1252
1253    #[allow(clippy::too_many_arguments)]
1254    fn execute_transaction<T>(
1255        &self,
1256        tx: MaybeCheckedTransaction,
1257        tx_id: &TxId,
1258        header: &PartialBlockHeader,
1259        coinbase_contract_id: ContractId,
1260        gas_price: Word,
1261        execution_data: &mut ExecutionData,
1262        storage_tx: &mut TxStorageTransaction<T>,
1263        memory: &mut MemoryInstance,
1264    ) -> ExecutorResult<Transaction>
1265    where
1266        T: KeyValueInspect<Column = Column>,
1267    {
1268        Self::check_mint_is_not_found(execution_data)?;
1269        Self::check_tx_is_not_duplicate(tx_id, storage_tx)?;
1270        let checked_tx = self.convert_maybe_checked_tx_to_checked_tx(tx, header)?;
1271
1272        match checked_tx {
1273            CheckedTransaction::Script(tx) => self.execute_chargeable_transaction(
1274                tx,
1275                header,
1276                coinbase_contract_id,
1277                gas_price,
1278                execution_data,
1279                storage_tx,
1280                memory,
1281            ),
1282            CheckedTransaction::Create(tx) => self.execute_chargeable_transaction(
1283                tx,
1284                header,
1285                coinbase_contract_id,
1286                gas_price,
1287                execution_data,
1288                storage_tx,
1289                memory,
1290            ),
1291            CheckedTransaction::Mint(mint) => self.execute_mint(
1292                mint,
1293                header,
1294                coinbase_contract_id,
1295                gas_price,
1296                execution_data,
1297                storage_tx,
1298            ),
1299            CheckedTransaction::Upgrade(tx) => self.execute_chargeable_transaction(
1300                tx,
1301                header,
1302                coinbase_contract_id,
1303                gas_price,
1304                execution_data,
1305                storage_tx,
1306                memory,
1307            ),
1308            CheckedTransaction::Upload(tx) => self.execute_chargeable_transaction(
1309                tx,
1310                header,
1311                coinbase_contract_id,
1312                gas_price,
1313                execution_data,
1314                storage_tx,
1315                memory,
1316            ),
1317            CheckedTransaction::Blob(tx) => self.execute_chargeable_transaction(
1318                tx,
1319                header,
1320                coinbase_contract_id,
1321                gas_price,
1322                execution_data,
1323                storage_tx,
1324                memory,
1325            ),
1326        }
1327    }
1328
1329    fn check_mint_is_not_found(execution_data: &ExecutionData) -> ExecutorResult<()> {
1330        if execution_data.found_mint {
1331            return Err(ExecutorError::MintIsNotLastTransaction)
1332        }
1333        Ok(())
1334    }
1335
1336    fn check_tx_is_not_duplicate<T>(
1337        tx_id: &TxId,
1338        storage_tx: &TxStorageTransaction<T>,
1339    ) -> ExecutorResult<()>
1340    where
1341        T: KeyValueInspect<Column = Column>,
1342    {
1343        if storage_tx
1344            .storage::<ProcessedTransactions>()
1345            .contains_key(tx_id)?
1346        {
1347            return Err(ExecutorError::TransactionIdCollision(*tx_id))
1348        }
1349        Ok(())
1350    }
1351
1352    fn convert_maybe_checked_tx_to_checked_tx(
1353        &self,
1354        tx: MaybeCheckedTransaction,
1355        header: &PartialBlockHeader,
1356    ) -> ExecutorResult<CheckedTransaction> {
1357        let block_height = *header.height();
1358        let actual_version = header.consensus_parameters_version;
1359        let expiration = tx.expiration();
1360        let checked_tx = match tx {
1361            MaybeCheckedTransaction::Transaction(tx) => tx
1362                .into_checked_basic(block_height, &self.consensus_params)?
1363                .into(),
1364            MaybeCheckedTransaction::CheckedTransaction(checked_tx, checked_version) => {
1365                // If you plan to add an other check of validity like this one on the checked_tx
1366                // then probably the `CheckedTransaction` type isn't useful anymore.
1367                if block_height > expiration {
1368                    return Err(ExecutorError::TransactionExpired(
1369                        expiration,
1370                        block_height,
1371                    ));
1372                }
1373                if actual_version == checked_version {
1374                    checked_tx
1375                } else {
1376                    let checked_tx: Checked<Transaction> = checked_tx.into();
1377                    let (tx, _) = checked_tx.into();
1378                    tx.into_checked_basic(block_height, &self.consensus_params)?
1379                        .into()
1380                }
1381            }
1382        };
1383        Ok(checked_tx)
1384    }
1385
1386    #[allow(clippy::too_many_arguments)]
1387    fn execute_mint<T>(
1388        &self,
1389        checked_mint: Checked<Mint>,
1390        header: &PartialBlockHeader,
1391        coinbase_contract_id: ContractId,
1392        gas_price: Word,
1393        execution_data: &mut ExecutionData,
1394        storage_tx: &mut TxStorageTransaction<T>,
1395    ) -> ExecutorResult<Transaction>
1396    where
1397        T: KeyValueInspect<Column = Column>,
1398    {
1399        execution_data.found_mint = true;
1400
1401        Self::check_mint_has_expected_index(&checked_mint, execution_data)?;
1402
1403        let coinbase_id = checked_mint.id();
1404        let (mut mint, _) = checked_mint.into();
1405
1406        Self::check_gas_price(&mint, gas_price)?;
1407        if mint.input_contract().contract_id == ContractId::zeroed() {
1408            Self::verify_mint_for_empty_contract(&mint)?;
1409        } else {
1410            Self::check_mint_amount(&mint, execution_data.coinbase)?;
1411
1412            let input = mint.input_contract().clone();
1413            let mut input = Input::Contract(input);
1414
1415            if self.options.forbid_fake_coins {
1416                self.verify_inputs_exist_and_values_match(
1417                    storage_tx,
1418                    core::slice::from_ref(&input),
1419                    header.da_height,
1420                )?;
1421            }
1422
1423            self.compute_inputs(core::slice::from_mut(&mut input), storage_tx)?;
1424
1425            let (input, output) = self.execute_mint_with_vm(
1426                header,
1427                coinbase_contract_id,
1428                execution_data,
1429                storage_tx,
1430                &coinbase_id,
1431                &mut mint,
1432                &mut input,
1433            )?;
1434
1435            *mint.input_contract_mut() = input;
1436            *mint.output_contract_mut() = output;
1437        }
1438
1439        Self::store_mint_tx(mint, execution_data, coinbase_id, storage_tx)
1440    }
1441
1442    #[allow(clippy::too_many_arguments)]
1443    fn execute_chargeable_transaction<Tx, T>(
1444        &self,
1445        mut checked_tx: Checked<Tx>,
1446        header: &PartialBlockHeader,
1447        coinbase_contract_id: ContractId,
1448        gas_price: Word,
1449        execution_data: &mut ExecutionData,
1450        storage_tx: &mut TxStorageTransaction<T>,
1451        memory: &mut MemoryInstance,
1452    ) -> ExecutorResult<Transaction>
1453    where
1454        Tx: ExecutableTransaction + Cacheable + Send + Sync + 'static,
1455        <Tx as IntoChecked>::Metadata: CheckedMetadataTrait + Send + Sync,
1456        T: KeyValueInspect<Column = Column>,
1457    {
1458        let tx_id = checked_tx.id();
1459
1460        if self.options.forbid_fake_coins {
1461            checked_tx = self.extra_tx_checks(checked_tx, header, storage_tx, memory)?;
1462        }
1463
1464        let (reverted, state, tx, receipts) = self.attempt_tx_execution_with_vm(
1465            checked_tx,
1466            header,
1467            coinbase_contract_id,
1468            gas_price,
1469            storage_tx,
1470            memory,
1471        )?;
1472
1473        self.spend_input_utxos(tx.inputs(), storage_tx, reverted, execution_data)?;
1474
1475        self.persist_output_utxos(
1476            *header.height(),
1477            execution_data,
1478            &tx_id,
1479            storage_tx,
1480            tx.inputs(),
1481            tx.outputs(),
1482        )?;
1483
1484        storage_tx
1485            .storage::<ProcessedTransactions>()
1486            .insert(&tx_id, &())?;
1487
1488        self.update_execution_data(
1489            &tx,
1490            execution_data,
1491            receipts,
1492            gas_price,
1493            reverted,
1494            state,
1495            tx_id,
1496        )?;
1497
1498        Ok(tx.into())
1499    }
1500
1501    fn check_mint_amount(mint: &Mint, expected_amount: u64) -> ExecutorResult<()> {
1502        if *mint.mint_amount() != expected_amount {
1503            return Err(ExecutorError::CoinbaseAmountMismatch)
1504        }
1505        Ok(())
1506    }
1507
1508    fn check_gas_price(mint: &Mint, expected_gas_price: Word) -> ExecutorResult<()> {
1509        if *mint.gas_price() != expected_gas_price {
1510            return Err(ExecutorError::CoinbaseGasPriceMismatch)
1511        }
1512        Ok(())
1513    }
1514
1515    fn check_mint_has_expected_index(
1516        checked_mint: &Checked<Mint>,
1517        execution_data: &ExecutionData,
1518    ) -> ExecutorResult<()> {
1519        if checked_mint.transaction().tx_pointer().tx_index() != execution_data.tx_count {
1520            return Err(ExecutorError::MintHasUnexpectedIndex)
1521        }
1522        Ok(())
1523    }
1524
1525    fn verify_mint_for_empty_contract(mint: &Mint) -> ExecutorResult<()> {
1526        if *mint.mint_amount() != 0 {
1527            return Err(ExecutorError::CoinbaseAmountMismatch)
1528        }
1529
1530        let input = input::contract::Contract {
1531            utxo_id: UtxoId::new(Bytes32::zeroed(), 0),
1532            balance_root: Bytes32::zeroed(),
1533            state_root: Bytes32::zeroed(),
1534            tx_pointer: TxPointer::new(BlockHeight::new(0), 0),
1535            contract_id: ContractId::zeroed(),
1536        };
1537        let output = output::contract::Contract {
1538            input_index: 0,
1539            balance_root: Bytes32::zeroed(),
1540            state_root: Bytes32::zeroed(),
1541        };
1542        if mint.input_contract() != &input || mint.output_contract() != &output {
1543            return Err(ExecutorError::MintMismatch)
1544        }
1545        Ok(())
1546    }
1547
1548    fn store_mint_tx<T>(
1549        mint: Mint,
1550        execution_data: &mut ExecutionData,
1551        coinbase_id: TxId,
1552        storage_tx: &mut TxStorageTransaction<T>,
1553    ) -> ExecutorResult<Transaction>
1554    where
1555        T: KeyValueInspect<Column = Column>,
1556    {
1557        let tx = mint.into();
1558
1559        execution_data.tx_status.push(TransactionExecutionStatus {
1560            id: coinbase_id,
1561            result: TransactionExecutionResult::Success {
1562                result: None,
1563                receipts: vec![],
1564                total_gas: 0,
1565                total_fee: 0,
1566            },
1567        });
1568
1569        if storage_tx
1570            .storage::<ProcessedTransactions>()
1571            .replace(&coinbase_id, &())?
1572            .is_some()
1573        {
1574            return Err(ExecutorError::TransactionIdCollision(coinbase_id))
1575        }
1576        Ok(tx)
1577    }
1578
1579    #[allow(clippy::too_many_arguments)]
1580    fn execute_mint_with_vm<T>(
1581        &self,
1582        header: &PartialBlockHeader,
1583        coinbase_contract_id: ContractId,
1584        execution_data: &mut ExecutionData,
1585        storage_tx: &mut TxStorageTransaction<T>,
1586        coinbase_id: &TxId,
1587        mint: &mut Mint,
1588        input: &mut Input,
1589    ) -> ExecutorResult<(input::contract::Contract, output::contract::Contract)>
1590    where
1591        T: KeyValueInspect<Column = Column>,
1592    {
1593        let mut sub_block_db_commit = storage_tx
1594            .write_transaction()
1595            .with_policy(ConflictPolicy::Overwrite);
1596
1597        let mut vm_db = VmStorage::new(
1598            &mut sub_block_db_commit,
1599            &header.consensus,
1600            &header.application,
1601            coinbase_contract_id,
1602        );
1603
1604        fuel_vm::interpreter::contract::balance_increase(
1605            &mut vm_db,
1606            &mint.input_contract().contract_id,
1607            mint.mint_asset_id(),
1608            *mint.mint_amount(),
1609        )
1610        .map_err(|e| format!("{e}"))
1611        .map_err(ExecutorError::CoinbaseCannotIncreaseBalance)?;
1612        sub_block_db_commit.commit()?;
1613
1614        let block_height = *header.height();
1615        let output = *mint.output_contract();
1616        let mut outputs = [Output::Contract(output)];
1617        self.persist_output_utxos(
1618            block_height,
1619            execution_data,
1620            coinbase_id,
1621            storage_tx,
1622            core::slice::from_ref(input),
1623            outputs.as_slice(),
1624        )?;
1625        self.compute_state_of_not_utxo_outputs(
1626            outputs.as_mut_slice(),
1627            core::slice::from_ref(input),
1628            *coinbase_id,
1629            storage_tx,
1630        )?;
1631        let Input::Contract(input) = core::mem::take(input) else {
1632            return Err(ExecutorError::Other(
1633                "Input of the `Mint` transaction is not a contract".to_string(),
1634            ))
1635        };
1636        let Output::Contract(output) = outputs[0] else {
1637            return Err(ExecutorError::Other(
1638                "The output of the `Mint` transaction is not a contract".to_string(),
1639            ))
1640        };
1641        Ok((input, output))
1642    }
1643
1644    fn update_tx_outputs<Tx, T>(
1645        &self,
1646        storage_tx: &TxStorageTransaction<T>,
1647        tx_id: TxId,
1648        tx: &mut Tx,
1649    ) -> ExecutorResult<()>
1650    where
1651        Tx: ExecutableTransaction,
1652        T: KeyValueInspect<Column = Column>,
1653    {
1654        let mut outputs = core::mem::take(tx.outputs_mut());
1655        self.compute_state_of_not_utxo_outputs(
1656            &mut outputs,
1657            tx.inputs(),
1658            tx_id,
1659            storage_tx,
1660        )?;
1661        *tx.outputs_mut() = outputs;
1662        Ok(())
1663    }
1664
1665    #[allow(clippy::too_many_arguments)]
1666    fn update_execution_data<Tx: Chargeable>(
1667        &self,
1668        tx: &Tx,
1669        execution_data: &mut ExecutionData,
1670        receipts: Vec<Receipt>,
1671        gas_price: Word,
1672        reverted: bool,
1673        state: ProgramState,
1674        tx_id: TxId,
1675    ) -> ExecutorResult<()> {
1676        let (used_gas, tx_fee) = self.total_fee_paid(tx, &receipts, gas_price)?;
1677        let used_size = tx.metered_bytes_size().try_into().unwrap_or(u32::MAX);
1678
1679        execution_data.coinbase = execution_data
1680            .coinbase
1681            .checked_add(tx_fee)
1682            .ok_or(ExecutorError::FeeOverflow)?;
1683        execution_data.used_gas = execution_data
1684            .used_gas
1685            .checked_add(used_gas)
1686            .ok_or_else(|| {
1687                ExecutorError::GasOverflow(
1688                    "Execution used gas overflowed.".into(),
1689                    execution_data.used_gas,
1690                    used_gas,
1691                )
1692            })?;
1693        execution_data.used_size = execution_data
1694            .used_size
1695            .checked_add(used_size)
1696            .ok_or(ExecutorError::TxSizeOverflow)?;
1697
1698        if !reverted {
1699            execution_data
1700                .message_ids
1701                .extend(receipts.iter().filter_map(|r| r.message_id()));
1702        }
1703
1704        let status = if reverted {
1705            TransactionExecutionResult::Failed {
1706                result: Some(state),
1707                receipts,
1708                total_gas: used_gas,
1709                total_fee: tx_fee,
1710            }
1711        } else {
1712            // else tx was a success
1713            TransactionExecutionResult::Success {
1714                result: Some(state),
1715                receipts,
1716                total_gas: used_gas,
1717                total_fee: tx_fee,
1718            }
1719        };
1720
1721        // queue up status for this tx to be stored once block id is finalized.
1722        execution_data.tx_status.push(TransactionExecutionStatus {
1723            id: tx_id,
1724            result: status,
1725        });
1726        Ok(())
1727    }
1728
1729    fn update_input_used_gas<Tx>(
1730        predicate_gas_used: Vec<Option<Word>>,
1731        tx_id: TxId,
1732        tx: &mut Tx,
1733    ) -> ExecutorResult<()>
1734    where
1735        Tx: ExecutableTransaction,
1736    {
1737        for (predicate_gas_used, produced_input) in
1738            predicate_gas_used.into_iter().zip(tx.inputs_mut())
1739        {
1740            if let Some(gas_used) = predicate_gas_used {
1741                match produced_input {
1742                    Input::CoinPredicate(CoinPredicate {
1743                        predicate_gas_used, ..
1744                    })
1745                    | Input::MessageCoinPredicate(MessageCoinPredicate {
1746                        predicate_gas_used,
1747                        ..
1748                    })
1749                    | Input::MessageDataPredicate(MessageDataPredicate {
1750                        predicate_gas_used,
1751                        ..
1752                    }) => {
1753                        *predicate_gas_used = gas_used;
1754                    }
1755                    _ => {
1756                        debug_assert!(
1757                            false,
1758                            "This error is not possible unless VM changes the order of inputs, \
1759                        or we added a new predicate inputs."
1760                        );
1761                        return Err(ExecutorError::InvalidTransactionOutcome {
1762                            transaction_id: tx_id,
1763                        })
1764                    }
1765                }
1766            }
1767        }
1768        Ok(())
1769    }
1770
1771    fn extra_tx_checks<Tx, T>(
1772        &self,
1773        mut checked_tx: Checked<Tx>,
1774        header: &PartialBlockHeader,
1775        storage_tx: &TxStorageTransaction<T>,
1776        memory: &mut MemoryInstance,
1777    ) -> ExecutorResult<Checked<Tx>>
1778    where
1779        Tx: ExecutableTransaction + Send + Sync + 'static,
1780        <Tx as IntoChecked>::Metadata: CheckedMetadataTrait + Send + Sync,
1781        T: KeyValueInspect<Column = Column>,
1782    {
1783        checked_tx = checked_tx
1784            .check_predicates(
1785                &CheckPredicateParams::from(&self.consensus_params),
1786                memory,
1787                storage_tx,
1788            )
1789            .map_err(|e| {
1790                ExecutorError::TransactionValidity(TransactionValidityError::Validation(
1791                    e,
1792                ))
1793            })?;
1794        debug_assert!(checked_tx.checks().contains(Checks::Predicates));
1795
1796        self.verify_inputs_exist_and_values_match(
1797            storage_tx,
1798            checked_tx.transaction().inputs(),
1799            header.da_height,
1800        )?;
1801        checked_tx = checked_tx
1802            .check_signatures(&self.consensus_params.chain_id())
1803            .map_err(TransactionValidityError::from)?;
1804        debug_assert!(checked_tx.checks().contains(Checks::Signatures));
1805        Ok(checked_tx)
1806    }
1807
1808    #[allow(clippy::type_complexity)]
1809    fn attempt_tx_execution_with_vm<Tx, T>(
1810        &self,
1811        checked_tx: Checked<Tx>,
1812        header: &PartialBlockHeader,
1813        coinbase_contract_id: ContractId,
1814        gas_price: Word,
1815        storage_tx: &mut TxStorageTransaction<T>,
1816        memory: &mut MemoryInstance,
1817    ) -> ExecutorResult<(bool, ProgramState, Tx, Vec<Receipt>)>
1818    where
1819        Tx: ExecutableTransaction + Cacheable,
1820        <Tx as IntoChecked>::Metadata: CheckedMetadataTrait + Send + Sync,
1821        T: KeyValueInspect<Column = Column>,
1822    {
1823        let tx_id = checked_tx.id();
1824
1825        let mut sub_block_db_commit = storage_tx
1826            .read_transaction()
1827            .with_policy(ConflictPolicy::Overwrite);
1828
1829        let vm_db = VmStorage::new(
1830            &mut sub_block_db_commit,
1831            &header.consensus,
1832            &header.application,
1833            coinbase_contract_id,
1834        );
1835
1836        let gas_costs = self.consensus_params.gas_costs();
1837        let fee_params = self.consensus_params.fee_params();
1838
1839        let predicate_gas_used = checked_tx
1840            .transaction()
1841            .inputs()
1842            .iter()
1843            .map(|input| input.predicate_gas_used())
1844            .collect();
1845        let ready_tx = checked_tx.into_ready(
1846            gas_price,
1847            gas_costs,
1848            fee_params,
1849            Some(*header.height()),
1850        )?;
1851
1852        let mut reverted;
1853
1854        let (state, mut tx, receipts) = if !self.options.dry_run {
1855            let mut vm = Interpreter::<_, _, _, NotSupportedEcal,
1856                verification::Normal>::with_storage(
1857                memory,
1858                vm_db,
1859                InterpreterParams::new(gas_price, &self.consensus_params),
1860            );
1861
1862            let vm_result: StateTransition<_> = vm
1863                .transact(ready_tx)
1864                .map_err(|error| ExecutorError::VmExecution {
1865                    error: error.to_string(),
1866                    transaction_id: tx_id,
1867                })?
1868                .into();
1869            reverted = vm_result.should_revert();
1870
1871            vm_result.into_inner()
1872        } else {
1873            let mut vm = Interpreter::<
1874                _,
1875                _,
1876                _,
1877                NotSupportedEcal,
1878                verification::AttemptContinue,
1879            >::with_storage(
1880                memory,
1881                vm_db,
1882                InterpreterParams::new(gas_price, &self.consensus_params),
1883            );
1884
1885            let vm_result: StateTransition<_> = vm
1886                .transact(ready_tx)
1887                .map_err(|error| ExecutorError::VmExecution {
1888                    error: error.to_string(),
1889                    transaction_id: tx_id,
1890                })?
1891                .into();
1892
1893            reverted = vm_result.should_revert();
1894
1895            let (state, tx, mut receipts) = vm_result.into_inner();
1896
1897            // If transaction requires contract ids, then extend receipts with
1898            // `PanicReason::ContractNotInInputs` for each missing contract id.
1899            // The data like `$pc` or `$is` is not available in this case,
1900            // because panic generated outside of the execution.
1901            if !vm.verifier().missing_contract_inputs.is_empty() {
1902                debug_assert!(self.options.dry_run);
1903                reverted = true;
1904
1905                let reason = PanicInstruction::error(
1906                    PanicReason::ContractNotInInputs,
1907                    op::noop().into(),
1908                );
1909
1910                for contract_id in &vm.verifier().missing_contract_inputs {
1911                    receipts.push(Receipt::Panic {
1912                        id: ContractId::zeroed(),
1913                        reason,
1914                        pc: 0,
1915                        is: 0,
1916                        contract_id: Some(*contract_id),
1917                    });
1918                }
1919            }
1920
1921            (state, tx, receipts)
1922        };
1923
1924        #[cfg(debug_assertions)]
1925        {
1926            tx.precompute(&self.consensus_params.chain_id())?;
1927            debug_assert_eq!(tx.id(&self.consensus_params.chain_id()), tx_id);
1928        }
1929
1930        Self::update_input_used_gas(predicate_gas_used, tx_id, &mut tx)?;
1931
1932        // We always need to update inputs with storage state before execution,
1933        // because VM zeroes malleable fields during the execution.
1934        self.compute_inputs(tx.inputs_mut(), storage_tx)?;
1935
1936        // only commit state changes if execution was a success
1937        if !reverted {
1938            let changes = sub_block_db_commit.into_changes();
1939            storage_tx.commit_changes(changes)?;
1940        }
1941
1942        self.update_tx_outputs(storage_tx, tx_id, &mut tx)?;
1943        Ok((reverted, state, tx, receipts.to_vec()))
1944    }
1945
1946    fn verify_inputs_exist_and_values_match<T>(
1947        &self,
1948        db: &StorageTransaction<T>,
1949        inputs: &[Input],
1950        block_da_height: DaBlockHeight,
1951    ) -> ExecutorResult<()>
1952    where
1953        T: KeyValueInspect<Column = Column>,
1954    {
1955        for input in inputs {
1956            match input {
1957                Input::CoinSigned(CoinSigned { utxo_id, .. })
1958                | Input::CoinPredicate(CoinPredicate { utxo_id, .. }) => {
1959                    match db.storage::<Coins>().get(utxo_id)? {
1960                        Some(coin) => {
1961                            if !coin.matches_input(input).unwrap_or_default() {
1962                                return Err(TransactionValidityError::CoinMismatch(
1963                                    *utxo_id,
1964                                )
1965                                .into())
1966                            }
1967                        }
1968                        _ => {
1969                            return Err(TransactionValidityError::CoinDoesNotExist(
1970                                *utxo_id,
1971                            )
1972                            .into())
1973                        }
1974                    }
1975                }
1976                Input::Contract(contract) => {
1977                    if !db
1978                        .storage::<ContractsLatestUtxo>()
1979                        .contains_key(&contract.contract_id)?
1980                    {
1981                        return Err(TransactionValidityError::ContractDoesNotExist(
1982                            contract.contract_id,
1983                        )
1984                        .into())
1985                    }
1986                }
1987                Input::MessageCoinSigned(MessageCoinSigned { nonce, .. })
1988                | Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. })
1989                | Input::MessageDataSigned(MessageDataSigned { nonce, .. })
1990                | Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => {
1991                    match db.storage::<Messages>().get(nonce)? {
1992                        Some(message) => {
1993                            if message.da_height() > block_da_height {
1994                                return Err(
1995                                    TransactionValidityError::MessageSpendTooEarly(
1996                                        *nonce,
1997                                    )
1998                                    .into(),
1999                                )
2000                            }
2001
2002                            if !message.matches_input(input).unwrap_or_default() {
2003                                return Err(TransactionValidityError::MessageMismatch(
2004                                    *nonce,
2005                                )
2006                                .into())
2007                            }
2008                        }
2009                        _ => {
2010                            return Err(TransactionValidityError::MessageDoesNotExist(
2011                                *nonce,
2012                            )
2013                            .into())
2014                        }
2015                    }
2016                }
2017            }
2018        }
2019
2020        Ok(())
2021    }
2022
2023    /// Mark input utxos as spent
2024    fn spend_input_utxos<T>(
2025        &self,
2026        inputs: &[Input],
2027        db: &mut TxStorageTransaction<T>,
2028        reverted: bool,
2029        execution_data: &mut ExecutionData,
2030    ) -> ExecutorResult<()>
2031    where
2032        T: KeyValueInspect<Column = Column>,
2033    {
2034        for input in inputs {
2035            match input {
2036                Input::CoinSigned(CoinSigned {
2037                    utxo_id,
2038                    owner,
2039                    amount,
2040                    asset_id,
2041                    ..
2042                })
2043                | Input::CoinPredicate(CoinPredicate {
2044                    utxo_id,
2045                    owner,
2046                    amount,
2047                    asset_id,
2048                    ..
2049                }) => {
2050                    // prune utxo from db
2051                    let coin = db
2052                        .storage::<Coins>()
2053                        .take(utxo_id)
2054                        .map_err(Into::into)
2055                        .transpose()
2056                        .unwrap_or_else(|| {
2057                            // If the coin is not found in the database, it means that it was
2058                            // already spent or `utxo_validation` is `false`.
2059                            self.get_coin_or_default(
2060                                db, *utxo_id, *owner, *amount, *asset_id,
2061                            )
2062                        })?;
2063
2064                    execution_data
2065                        .events
2066                        .push(ExecutorEvent::CoinConsumed(coin.uncompress(*utxo_id)));
2067                }
2068                Input::MessageDataSigned(_) | Input::MessageDataPredicate(_)
2069                    if reverted =>
2070                {
2071                    // Don't spend the retryable messages if transaction is reverted
2072                    continue
2073                }
2074                Input::MessageCoinSigned(MessageCoinSigned { nonce, .. })
2075                | Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. })
2076                | Input::MessageDataSigned(MessageDataSigned { nonce, .. })
2077                | Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => {
2078                    // ensure message wasn't already marked as spent,
2079                    // and cleanup message contents
2080                    let message = db
2081                        .storage::<Messages>()
2082                        .take(nonce)?
2083                        .ok_or(ExecutorError::MessageDoesNotExist(*nonce))?;
2084                    execution_data
2085                        .events
2086                        .push(ExecutorEvent::MessageConsumed(message));
2087                }
2088                _ => {}
2089            }
2090        }
2091        Ok(())
2092    }
2093
2094    fn total_fee_paid<Tx: Chargeable>(
2095        &self,
2096        tx: &Tx,
2097        receipts: &[Receipt],
2098        gas_price: Word,
2099    ) -> ExecutorResult<(Word, Word)> {
2100        let min_gas = tx.min_gas(
2101            self.consensus_params.gas_costs(),
2102            self.consensus_params.fee_params(),
2103        );
2104        let max_fee = tx.max_fee_limit();
2105        let mut used_gas = 0;
2106        for r in receipts {
2107            if let Receipt::ScriptResult { gas_used, .. } = r {
2108                used_gas = *gas_used;
2109                break
2110            }
2111        }
2112
2113        let fee = tx
2114            .refund_fee(
2115                self.consensus_params.gas_costs(),
2116                self.consensus_params.fee_params(),
2117                used_gas,
2118                gas_price,
2119            )
2120            .ok_or(ExecutorError::FeeOverflow)?;
2121        let total_used_gas = min_gas.checked_add(used_gas).ok_or_else(|| {
2122            ExecutorError::GasOverflow(
2123                "Total used gas overflowed.".into(),
2124                min_gas,
2125                used_gas,
2126            )
2127        })?;
2128        // if there's no script result (i.e. create) then fee == base amount
2129        Ok((
2130            total_used_gas,
2131            max_fee.checked_sub(fee).ok_or(ExecutorError::FeeOverflow)?,
2132        ))
2133    }
2134
2135    /// Computes all zeroed or variable inputs.
2136    fn compute_inputs<T>(
2137        &self,
2138        inputs: &mut [Input],
2139        db: &TxStorageTransaction<T>,
2140    ) -> ExecutorResult<()>
2141    where
2142        T: KeyValueInspect<Column = Column>,
2143    {
2144        for input in inputs {
2145            match input {
2146                Input::CoinSigned(CoinSigned {
2147                    tx_pointer,
2148                    utxo_id,
2149                    owner,
2150                    amount,
2151                    asset_id,
2152                    ..
2153                })
2154                | Input::CoinPredicate(CoinPredicate {
2155                    tx_pointer,
2156                    utxo_id,
2157                    owner,
2158                    amount,
2159                    asset_id,
2160                    ..
2161                }) => {
2162                    let coin = self
2163                        .get_coin_or_default(db, *utxo_id, *owner, *amount, *asset_id)?;
2164                    *tx_pointer = *coin.tx_pointer();
2165                }
2166                &mut Input::Contract(input::contract::Contract {
2167                    ref mut utxo_id,
2168                    ref mut balance_root,
2169                    ref mut state_root,
2170                    ref mut tx_pointer,
2171                    ref contract_id,
2172                    ..
2173                }) => {
2174                    let contract = ContractRef::new(db, *contract_id);
2175                    let utxo_info =
2176                        contract.validated_utxo(self.options.forbid_fake_coins)?;
2177                    *utxo_id = *utxo_info.utxo_id();
2178                    *tx_pointer = utxo_info.tx_pointer();
2179                    *balance_root = contract.balance_root()?;
2180                    *state_root = contract.state_root()?;
2181                }
2182                _ => {}
2183            }
2184        }
2185        Ok(())
2186    }
2187
2188    #[allow(clippy::type_complexity)]
2189    // TODO: Maybe we need move it to `fuel-vm`? O_o Because other `Outputs` are processed there
2190    /// Computes all zeroed or variable outputs.
2191    /// In production mode, updates the outputs with computed values.
2192    /// In validation mode, compares the outputs with computed inputs.
2193    fn compute_state_of_not_utxo_outputs<T>(
2194        &self,
2195        outputs: &mut [Output],
2196        inputs: &[Input],
2197        tx_id: TxId,
2198        db: &TxStorageTransaction<T>,
2199    ) -> ExecutorResult<()>
2200    where
2201        T: KeyValueInspect<Column = Column>,
2202    {
2203        for output in outputs {
2204            if let Output::Contract(contract_output) = output {
2205                let contract_id =
2206                    if let Some(Input::Contract(input::contract::Contract {
2207                        contract_id,
2208                        ..
2209                    })) = inputs.get(contract_output.input_index as usize)
2210                    {
2211                        contract_id
2212                    } else {
2213                        return Err(ExecutorError::InvalidTransactionOutcome {
2214                            transaction_id: tx_id,
2215                        })
2216                    };
2217
2218                let contract = ContractRef::new(db, *contract_id);
2219                contract_output.balance_root = contract.balance_root()?;
2220                contract_output.state_root = contract.state_root()?;
2221            }
2222        }
2223        Ok(())
2224    }
2225
2226    #[allow(clippy::too_many_arguments)]
2227    pub fn get_coin_or_default<T>(
2228        &self,
2229        db: &TxStorageTransaction<T>,
2230        utxo_id: UtxoId,
2231        owner: Address,
2232        amount: u64,
2233        asset_id: AssetId,
2234    ) -> ExecutorResult<CompressedCoin>
2235    where
2236        T: KeyValueInspect<Column = Column>,
2237    {
2238        if self.options.forbid_fake_coins {
2239            db.storage::<Coins>()
2240                .get(&utxo_id)?
2241                .ok_or(ExecutorError::TransactionValidity(
2242                    TransactionValidityError::CoinDoesNotExist(utxo_id),
2243                ))
2244                .map(Cow::into_owned)
2245        } else {
2246            // if utxo validation is disabled, just assign this new input to the original block
2247            let coin = CompressedCoinV1 {
2248                owner,
2249                amount,
2250                asset_id,
2251                tx_pointer: Default::default(),
2252            }
2253            .into();
2254            Ok(coin)
2255        }
2256    }
2257
2258    fn persist_output_utxos<T>(
2259        &self,
2260        block_height: BlockHeight,
2261        execution_data: &mut ExecutionData,
2262        tx_id: &Bytes32,
2263        db: &mut TxStorageTransaction<T>,
2264        inputs: &[Input],
2265        outputs: &[Output],
2266    ) -> ExecutorResult<()>
2267    where
2268        T: KeyValueInspect<Column = Column>,
2269    {
2270        let tx_idx = execution_data.tx_count;
2271        for (output_index, output) in outputs.iter().enumerate() {
2272            let index =
2273                u16::try_from(output_index).map_err(|_| ExecutorError::TooManyOutputs)?;
2274            let utxo_id = UtxoId::new(*tx_id, index);
2275            match output {
2276                Output::Coin {
2277                    amount,
2278                    asset_id,
2279                    to,
2280                } => Self::insert_coin(
2281                    block_height,
2282                    execution_data,
2283                    utxo_id,
2284                    amount,
2285                    asset_id,
2286                    to,
2287                    db,
2288                )?,
2289                Output::Contract(contract) => {
2290                    if let Some(Input::Contract(input::contract::Contract {
2291                        contract_id,
2292                        ..
2293                    })) = inputs.get(contract.input_index as usize)
2294                    {
2295                        let tx_pointer = TxPointer::new(block_height, tx_idx);
2296                        db.storage::<ContractsLatestUtxo>().insert(
2297                            contract_id,
2298                            &ContractUtxoInfo::V1((utxo_id, tx_pointer).into()),
2299                        )?;
2300                    } else {
2301                        return Err(ExecutorError::TransactionValidity(
2302                            TransactionValidityError::InvalidContractInputIndex(utxo_id),
2303                        ))
2304                    }
2305                }
2306                Output::Change {
2307                    to,
2308                    asset_id,
2309                    amount,
2310                } => Self::insert_coin(
2311                    block_height,
2312                    execution_data,
2313                    utxo_id,
2314                    amount,
2315                    asset_id,
2316                    to,
2317                    db,
2318                )?,
2319                Output::Variable {
2320                    to,
2321                    asset_id,
2322                    amount,
2323                } => Self::insert_coin(
2324                    block_height,
2325                    execution_data,
2326                    utxo_id,
2327                    amount,
2328                    asset_id,
2329                    to,
2330                    db,
2331                )?,
2332                Output::ContractCreated { contract_id, .. } => {
2333                    let tx_pointer = TxPointer::new(block_height, tx_idx);
2334                    db.storage::<ContractsLatestUtxo>().insert(
2335                        contract_id,
2336                        &ContractUtxoInfo::V1((utxo_id, tx_pointer).into()),
2337                    )?;
2338                }
2339            }
2340        }
2341        Ok(())
2342    }
2343
2344    fn insert_coin<T>(
2345        block_height: BlockHeight,
2346        execution_data: &mut ExecutionData,
2347        utxo_id: UtxoId,
2348        amount: &Word,
2349        asset_id: &AssetId,
2350        to: &Address,
2351        db: &mut TxStorageTransaction<T>,
2352    ) -> ExecutorResult<()>
2353    where
2354        T: KeyValueInspect<Column = Column>,
2355    {
2356        // Only insert a coin output if it has some amount.
2357        // This is because variable or transfer outputs won't have any value
2358        // if there's a revert or panic and shouldn't be added to the utxo set.
2359        if *amount > Word::MIN {
2360            let coin = CompressedCoinV1 {
2361                owner: *to,
2362                amount: *amount,
2363                asset_id: *asset_id,
2364                tx_pointer: TxPointer::new(block_height, execution_data.tx_count),
2365            }
2366            .into();
2367
2368            if db.storage::<Coins>().replace(&utxo_id, &coin)?.is_some() {
2369                return Err(ExecutorError::OutputAlreadyExists)
2370            }
2371            execution_data
2372                .events
2373                .push(ExecutorEvent::CoinCreated(coin.uncompress(utxo_id)));
2374        }
2375
2376        Ok(())
2377    }
2378}