//! Types related to executor service. use crate::{ blockchain::{ block::{ Block, PartialFuelBlock, }, header::ConsensusParametersVersion, primitives::BlockId, }, entities::{ coins::coin::Coin, relayer::{ message::Message, transaction::RelayedTransactionId, }, }, fuel_tx::{ Receipt, TxId, UtxoId, ValidityError, }, fuel_types::{ BlockHeight, Bytes32, ContractId, Nonce, }, fuel_vm::{ checked_transaction::CheckError, ProgramState, }, services::Uncommitted, }; /// The alias for executor result. pub type Result = core::result::Result; /// The uncommitted result of the transaction execution. pub type UncommittedResult = Uncommitted; /// The result of transactions execution. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug)] pub struct ExecutionResult { /// Created block during the execution of transactions. It contains only valid transactions. pub block: Block, /// The list of skipped transactions with corresponding errors. Those transactions were /// not included in the block and didn't affect the state of the blockchain. pub skipped_transactions: Vec<(TxId, Error)>, /// The status of the transactions execution included into the block. pub tx_status: Vec, /// The list of all events generated during the execution of the block. pub events: Vec, } /// The event represents some internal state changes caused by the block execution. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Event { /// Imported a new spendable message from the relayer. MessageImported(Message), /// The message was consumed by the transaction. MessageConsumed(Message), /// Created a new spendable coin, produced by the transaction. CoinCreated(Coin), /// The coin was consumed by the transaction. CoinConsumed(Coin), /// Failed transaction inclusion ForcedTransactionFailed { /// The hash of the relayed transaction id: RelayedTransactionId, /// The height of the block that processed this transaction block_height: BlockHeight, /// The actual failure reason for why the forced transaction was not included failure: String, }, } /// Known failure modes for processing forced transactions #[derive(Debug, derive_more::Display)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum ForcedTransactionFailure { /// Failed to decode transaction to a valid fuel_tx::Transaction #[display(fmt = "Failed to decode transaction")] CodecError, /// Transaction failed basic checks #[display(fmt = "Failed validity checks: {_0:?}")] CheckError(CheckError), /// Invalid transaction type #[display(fmt = "Transaction type is not accepted")] InvalidTransactionType, /// Execution error which failed to include #[display(fmt = "Transaction inclusion failed {_0}")] ExecutionError(Error), /// Relayed Transaction didn't specify high enough max gas #[display( fmt = "Insufficient max gas: Expected: {claimed_max_gas:?}, Actual: {actual_max_gas:?}" )] InsufficientMaxGas { /// The max gas claimed by the L1 transaction submitter claimed_max_gas: u64, /// The actual max gas used by the transaction actual_max_gas: u64, }, } /// The status of a transaction after it is executed. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TransactionExecutionStatus { /// The id of the transaction. pub id: Bytes32, /// The result of the executed transaction. pub result: TransactionExecutionResult, } /// The result of transaction execution. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum TransactionExecutionResult { /// Transaction was successfully executed. Success { /// The result of successful transaction execution. result: Option, /// The receipts generated by the executed transaction. receipts: Vec, /// The total gas used by the transaction. total_gas: u64, /// The total fee paid by the transaction. total_fee: u64, }, /// The execution of the transaction failed. Failed { /// The result of failed transaction execution. result: Option, /// The receipts generated by the executed transaction. receipts: Vec, /// The total gas used by the transaction. total_gas: u64, /// The total fee paid by the transaction. total_fee: u64, }, } impl TransactionExecutionResult { /// Get the receipts generated by the executed transaction. pub fn receipts(&self) -> &[Receipt] { match self { TransactionExecutionResult::Success { receipts, .. } | TransactionExecutionResult::Failed { receipts, .. } => receipts, } } /// Get the reason of the failed transaction execution. pub fn reason(receipts: &[Receipt], state: &Option) -> String { receipts .iter() .find_map(|receipt| match receipt { // Format as `Revert($rA)` Receipt::Revert { ra, .. } => Some(format!("Revert({ra})")), // Display PanicReason e.g. `OutOfGas` Receipt::Panic { reason, .. } => Some(format!("{}", reason.reason())), _ => None, }) .unwrap_or_else(|| format!("{:?}", &state)) } } /// Execution wrapper where the types /// depend on the type of execution. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ExecutionTypes { /// DryRun mode where P is being produced. DryRun(P), /// Production mode where P is being produced. Production(P), /// Validation mode where V is being checked. Validation(V), } /// Starting point for executing a block. Production starts with a [`PartialFuelBlock`]. /// Validation starts with a full `FuelBlock`. pub type ExecutionBlock = ExecutionTypes; impl

ExecutionTypes { /// Get the hash of the full `FuelBlock` if validating. pub fn id(&self) -> Option { match self { ExecutionTypes::DryRun(_) => None, ExecutionTypes::Production(_) => None, ExecutionTypes::Validation(v) => Some(v.id()), } } } // TODO: Move `ExecutionType` and `ExecutionKind` into `fuel-core-executor` /// Execution wrapper with only a single type. pub type ExecutionType = ExecutionTypes; impl ExecutionTypes { /// Map the production type if producing. pub fn map_p(self, f: F) -> ExecutionTypes where F: FnOnce(P) -> Q, { match self { ExecutionTypes::DryRun(p) => ExecutionTypes::DryRun(f(p)), ExecutionTypes::Production(p) => ExecutionTypes::Production(f(p)), ExecutionTypes::Validation(v) => ExecutionTypes::Validation(v), } } /// Map the validation type if validating. pub fn map_v(self, f: F) -> ExecutionTypes where F: FnOnce(V) -> W, { match self { ExecutionTypes::DryRun(p) => ExecutionTypes::DryRun(p), ExecutionTypes::Production(p) => ExecutionTypes::Production(p), ExecutionTypes::Validation(v) => ExecutionTypes::Validation(f(v)), } } /// Get a reference version of the inner type. pub fn as_ref(&self) -> ExecutionTypes<&P, &V> { match *self { ExecutionTypes::DryRun(ref p) => ExecutionTypes::DryRun(p), ExecutionTypes::Production(ref p) => ExecutionTypes::Production(p), ExecutionTypes::Validation(ref v) => ExecutionTypes::Validation(v), } } /// Get a mutable reference version of the inner type. pub fn as_mut(&mut self) -> ExecutionTypes<&mut P, &mut V> { match *self { ExecutionTypes::DryRun(ref mut p) => ExecutionTypes::DryRun(p), ExecutionTypes::Production(ref mut p) => ExecutionTypes::Production(p), ExecutionTypes::Validation(ref mut v) => ExecutionTypes::Validation(v), } } /// Get the kind of execution. pub fn to_kind(&self) -> ExecutionKind { match self { ExecutionTypes::DryRun(_) => ExecutionKind::DryRun, ExecutionTypes::Production(_) => ExecutionKind::Production, ExecutionTypes::Validation(_) => ExecutionKind::Validation, } } } impl ExecutionType { /// Map the wrapped type. pub fn map(self, f: F) -> ExecutionType where F: FnOnce(T) -> U, { match self { ExecutionTypes::DryRun(p) => ExecutionTypes::DryRun(f(p)), ExecutionTypes::Production(p) => ExecutionTypes::Production(f(p)), ExecutionTypes::Validation(v) => ExecutionTypes::Validation(f(v)), } } /// Filter and map the inner type. pub fn filter_map(self, f: F) -> Option> where F: FnOnce(T) -> Option, { match self { ExecutionTypes::DryRun(p) => f(p).map(ExecutionTypes::DryRun), ExecutionTypes::Production(p) => f(p).map(ExecutionTypes::Production), ExecutionTypes::Validation(v) => f(v).map(ExecutionTypes::Validation), } } /// Get the inner type. pub fn into_inner(self) -> T { match self { ExecutionTypes::DryRun(t) | ExecutionTypes::Production(t) | ExecutionTypes::Validation(t) => t, } } /// Split into the execution kind and the inner type. pub fn split(self) -> (ExecutionKind, T) { let kind = self.to_kind(); (kind, self.into_inner()) } } impl core::ops::Deref for ExecutionType { type Target = T; fn deref(&self) -> &Self::Target { match self { ExecutionTypes::DryRun(p) => p, ExecutionTypes::Production(p) => p, ExecutionTypes::Validation(v) => v, } } } impl core::ops::DerefMut for ExecutionType { fn deref_mut(&mut self) -> &mut Self::Target { match self { ExecutionTypes::DryRun(p) => p, ExecutionTypes::Production(p) => p, ExecutionTypes::Validation(v) => v, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// The kind of execution. pub enum ExecutionKind { /// Dry run a block. DryRun, /// Producing a block. Production, /// Validating a block. Validation, } impl ExecutionKind { /// Wrap a type in this execution kind. pub fn wrap(self, t: T) -> ExecutionType { match self { ExecutionKind::DryRun => ExecutionTypes::DryRun(t), ExecutionKind::Production => ExecutionTypes::Production(t), ExecutionKind::Validation => ExecutionTypes::Validation(t), } } } #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, derive_more::Display, derive_more::From)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum Error { #[display(fmt = "Transaction id was already used: {_0:#x}")] TransactionIdCollision(Bytes32), #[display(fmt = "Too many transactions in the block")] TooManyTransactions, #[display(fmt = "output already exists")] OutputAlreadyExists, #[display(fmt = "The computed fee caused an integer overflow")] FeeOverflow, #[display(fmt = "The computed gas caused an integer overflow")] GasOverflow, #[display(fmt = "The block is missing `Mint` transaction.")] MintMissing, #[display(fmt = "Found the second entry of the `Mint` transaction in the block.")] MintFoundSecondEntry, #[display(fmt = "The `Mint` transaction has an unexpected index.")] MintHasUnexpectedIndex, #[display(fmt = "The last transaction in the block is not `Mint`.")] MintIsNotLastTransaction, #[display(fmt = "The `Mint` transaction mismatches expectations.")] MintMismatch, #[display(fmt = "Can't increase the balance of the coinbase contract: {_0}.")] CoinbaseCannotIncreaseBalance(String), #[display(fmt = "Coinbase amount mismatches with expected.")] CoinbaseAmountMismatch, #[display(fmt = "Coinbase gas price mismatches with expected.")] CoinbaseGasPriceMismatch, #[from] TransactionValidity(TransactionValidityError), // TODO: Replace with `fuel_core_storage::Error` when execution error will live in the // `fuel-core-executor`. #[display(fmt = "got error during work with storage {_0}")] StorageError(String), #[display(fmt = "got error during work with relayer {_0}")] RelayerError(String), #[display(fmt = "Transaction({transaction_id:#x}) execution error: {error:?}")] VmExecution { // TODO: Use `InterpreterError` when `InterpreterError` implements serde error: String, transaction_id: Bytes32, }, #[display(fmt = "{_0:?}")] InvalidTransaction(CheckError), #[display(fmt = "Transaction doesn't match expected result: {transaction_id:#x}")] InvalidTransactionOutcome { transaction_id: Bytes32 }, #[display(fmt = "The amount of charged fees is invalid")] InvalidFeeAmount, #[display(fmt = "Block id is invalid")] InvalidBlockId, #[display(fmt = "No matching utxo for contract id ${_0:#x}")] ContractUtxoMissing(ContractId), #[display(fmt = "message already spent {_0:#x}")] MessageDoesNotExist(Nonce), #[display(fmt = "Expected input of type {_0}")] InputTypeMismatch(String), #[display(fmt = "Executing of the genesis block is not allowed")] ExecutingGenesisBlock, #[display(fmt = "The da height exceeded its maximum limit")] DaHeightExceededItsLimit, #[display(fmt = "Unable to find the previous block to fetch the DA height")] PreviousBlockIsNotFound, #[display(fmt = "The relayer gives incorrect messages for the requested da height")] RelayerGivesIncorrectMessages, #[display(fmt = "Consensus parameters not found for version {_0}")] ConsensusParametersNotFound(ConsensusParametersVersion), /// It is possible to occur untyped errors in the case of the upgrade. #[display(fmt = "Occurred untyped error: {_0}")] Other(String), } impl From for anyhow::Error { fn from(error: Error) -> Self { anyhow::Error::msg(error) } } impl From for Error { fn from(e: CheckError) -> Self { Self::InvalidTransaction(e) } } impl From for Error { fn from(e: ValidityError) -> Self { Self::InvalidTransaction(CheckError::Validity(e)) } } #[allow(missing_docs)] #[derive(thiserror::Error, Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum TransactionValidityError { #[error("Coin({0:#x}) input was already spent")] CoinAlreadySpent(UtxoId), #[error("The input coin({0:#x}) doesn't match the coin from database")] CoinMismatch(UtxoId), #[error("The specified coin({0:#x}) doesn't exist")] CoinDoesNotExist(UtxoId), #[error( "Message({0:#x}) is not yet spendable, as it's DA height is newer than this block allows" )] MessageSpendTooEarly(Nonce), #[error("The specified message({0:#x}) doesn't exist, possibly because it was already spent")] MessageDoesNotExist(Nonce), #[error("The input message({0:#x}) doesn't match the relayer message")] MessageMismatch(Nonce), #[error("The specified contract({0:#x}) doesn't exist")] ContractDoesNotExist(ContractId), #[error("Contract output index isn't valid: {0:#x}")] InvalidContractInputIndex(UtxoId), #[error("Transaction validity: {0:#?}")] Validation(CheckError), } impl From for TransactionValidityError { fn from(e: CheckError) -> Self { Self::Validation(e) } } impl From for TransactionValidityError { fn from(e: ValidityError) -> Self { Self::Validation(CheckError::Validity(e)) } }