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