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