fuel_core_executor/
executor.rs

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