Skip to main content

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