fuel_core_executor/
executor.rs

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