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