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#[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 let transactions_limit = (transactions_limit as usize).min(transactions.len());
210 transactions.drain(..transactions_limit).collect()
211 }
212}
213
214#[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#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]
250pub struct ExecutionOptions {
251 pub extra_tx_checks: bool,
254 pub backtrace: bool,
256}
257
258#[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 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 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 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 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 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 TransactionExecutionResult::Success {
1511 result: Some(state),
1512 receipts,
1513 total_gas: used_gas,
1514 total_fee: tx_fee,
1515 }
1516 };
1517
1518 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 self.compute_inputs(tx.inputs_mut(), storage_tx)?;
1672
1673 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 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 let coin = db
1778 .storage::<Coins>()
1779 .take(utxo_id)
1780 .map_err(Into::into)
1781 .transpose()
1782 .unwrap_or_else(|| {
1783 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 continue
1799 }
1800 Input::MessageCoinSigned(MessageCoinSigned { nonce, .. })
1801 | Input::MessageCoinPredicate(MessageCoinPredicate { nonce, .. })
1802 | Input::MessageDataSigned(MessageDataSigned { nonce, .. })
1803 | Input::MessageDataPredicate(MessageDataPredicate { nonce, .. }) => {
1804 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 Ok((
1857 total_used_gas,
1858 max_fee.checked_sub(fee).ok_or(ExecutorError::FeeOverflow)?,
1859 ))
1860 }
1861
1862 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 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 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 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")), );
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 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}