use crate::{
common::{
fuel_tx::{
CheckError,
TxId,
UtxoId,
},
fuel_types::{
Bytes32,
ContractId,
MessageId,
},
fuel_vm::backtrace::Backtrace,
},
db::KvStoreError,
model::{
BlockId,
FuelBlock,
PartialFuelBlock,
},
txpool::TransactionStatus,
};
use fuel_vm::fuel_tx::Transaction;
use std::error::Error as StdError;
use thiserror::Error;
pub type ExecutionBlock = ExecutionTypes<PartialFuelBlock, FuelBlock>;
pub type ExecutionType<T> = ExecutionTypes<T, T>;
#[derive(Debug, Clone, Copy)]
pub enum ExecutionTypes<P, V> {
Production(P),
Validation(V),
}
#[derive(Debug)]
pub struct ExecutionResult {
pub block: FuelBlock,
pub skipped_transactions: Vec<(Transaction, Error)>,
pub tx_status: Vec<TransactionExecutionStatus>,
}
#[derive(Debug, Clone)]
pub struct TransactionExecutionStatus {
pub id: Bytes32,
pub status: TransactionStatus,
}
#[derive(Debug)]
pub struct UncommittedResult<DbTransaction> {
result: ExecutionResult,
database_transaction: DbTransaction,
}
impl<DbTransaction> UncommittedResult<DbTransaction> {
pub fn new(result: ExecutionResult, database_transaction: DbTransaction) -> Self {
Self {
result,
database_transaction,
}
}
pub fn result(&self) -> &ExecutionResult {
&self.result
}
pub fn into(self) -> (ExecutionResult, DbTransaction) {
(self.result, self.database_transaction)
}
pub fn into_result(self) -> ExecutionResult {
self.result
}
pub fn into_transaction(self) -> DbTransaction {
self.database_transaction
}
}
#[derive(Debug, Clone, Copy)]
pub enum ExecutionKind {
Production,
Validation,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum TransactionValidityError {
#[error("Coin input was already spent")]
CoinAlreadySpent(UtxoId),
#[error("Coin has not yet reached maturity")]
CoinHasNotMatured(UtxoId),
#[error("The specified coin doesn't exist")]
CoinDoesNotExist(UtxoId),
#[error("The specified message was already spent")]
MessageAlreadySpent(MessageId),
#[error(
"Message is not yet spendable, as it's DA height is newer than this block allows"
)]
MessageSpendTooEarly(MessageId),
#[error("The specified message doesn't exist")]
MessageDoesNotExist(MessageId),
#[error("Contract output index isn't valid: {0:#x}")]
InvalidContractInputIndex(UtxoId),
#[error("The transaction must have at least one coin or message input type: {0:#x}")]
NoCoinOrMessageInput(TxId),
#[error("The transaction contains predicate inputs which aren't enabled: {0:#x}")]
PredicateExecutionDisabled(TxId),
#[error(
"The transaction contains a predicate which failed to validate: TransactionId({0:#x})"
)]
InvalidPredicate(TxId),
#[error("Transaction validity: {0:#?}")]
Validation(#[from] CheckError),
#[error("Datastore error occurred")]
DataStoreError(Box<dyn StdError + Send + Sync>),
}
impl From<KvStoreError> for TransactionValidityError {
fn from(e: KvStoreError) -> Self {
Self::DataStoreError(Box::new(e))
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("Transaction id was already used: {0:#x}")]
TransactionIdCollision(Bytes32),
#[error("output already exists")]
OutputAlreadyExists,
#[error("The computed fee caused an integer overflow")]
FeeOverflow,
#[error("Not supported transaction: {0:?}")]
NotSupportedTransaction(Box<Transaction>),
#[error("The first transaction in the block is not `Mint` - coinbase.")]
CoinbaseIsNotFirstTransaction,
#[error("Coinbase should have one output.")]
CoinbaseSeveralOutputs,
#[error("Coinbase outputs is invalid.")]
CoinbaseOutputIsInvalid,
#[error("Coinbase amount mismatches with expected.")]
CoinbaseAmountMismatch,
#[error("Invalid transaction: {0}")]
TransactionValidity(#[from] TransactionValidityError),
#[error("corrupted block state")]
CorruptedBlockState(Box<dyn StdError + Send + Sync>),
#[error("Transaction({transaction_id:#x}) execution error: {error:?}")]
VmExecution {
error: fuel_vm::prelude::InterpreterError,
transaction_id: Bytes32,
},
#[error(transparent)]
InvalidTransaction(#[from] CheckError),
#[error("Execution error with backtrace")]
Backtrace(Box<Backtrace>),
#[error("Transaction doesn't match expected result: {transaction_id:#x}")]
InvalidTransactionOutcome { transaction_id: Bytes32 },
#[error("Transaction root is invalid")]
InvalidTransactionRoot,
#[error("The amount of charged fees is invalid")]
InvalidFeeAmount,
#[error("Block id is invalid")]
InvalidBlockId,
#[error("No matching utxo for contract id ${0:#x}")]
ContractUtxoMissing(ContractId),
}
impl ExecutionBlock {
pub fn id(&self) -> Option<BlockId> {
match self {
ExecutionTypes::Production(_) => None,
ExecutionTypes::Validation(v) => Some(v.id()),
}
}
pub fn txs_root(&self) -> Option<Bytes32> {
match self {
ExecutionTypes::Production(_) => None,
ExecutionTypes::Validation(v) => Some(v.header().transactions_root),
}
}
}
impl<P, V> ExecutionTypes<P, V> {
pub fn map_p<Q, F>(self, f: F) -> ExecutionTypes<Q, V>
where
F: FnOnce(P) -> Q,
{
match self {
ExecutionTypes::Production(p) => ExecutionTypes::Production(f(p)),
ExecutionTypes::Validation(v) => ExecutionTypes::Validation(v),
}
}
pub fn map_v<W, F>(self, f: F) -> ExecutionTypes<P, W>
where
F: FnOnce(V) -> W,
{
match self {
ExecutionTypes::Production(p) => ExecutionTypes::Production(p),
ExecutionTypes::Validation(v) => ExecutionTypes::Validation(f(v)),
}
}
pub fn as_ref(&self) -> ExecutionTypes<&P, &V> {
match *self {
ExecutionTypes::Production(ref p) => ExecutionTypes::Production(p),
ExecutionTypes::Validation(ref v) => ExecutionTypes::Validation(v),
}
}
pub fn as_mut(&mut self) -> ExecutionTypes<&mut P, &mut V> {
match *self {
ExecutionTypes::Production(ref mut p) => ExecutionTypes::Production(p),
ExecutionTypes::Validation(ref mut v) => ExecutionTypes::Validation(v),
}
}
pub fn to_kind(&self) -> ExecutionKind {
match self {
ExecutionTypes::Production(_) => ExecutionKind::Production,
ExecutionTypes::Validation(_) => ExecutionKind::Validation,
}
}
}
impl<T> ExecutionType<T> {
pub fn map<U, F>(self, f: F) -> ExecutionType<U>
where
F: FnOnce(T) -> U,
{
match self {
ExecutionTypes::Production(p) => ExecutionTypes::Production(f(p)),
ExecutionTypes::Validation(v) => ExecutionTypes::Validation(f(v)),
}
}
pub fn filter_map<U, F>(self, f: F) -> Option<ExecutionType<U>>
where
F: FnOnce(T) -> Option<U>,
{
match self {
ExecutionTypes::Production(p) => f(p).map(ExecutionTypes::Production),
ExecutionTypes::Validation(v) => f(v).map(ExecutionTypes::Validation),
}
}
pub fn into_inner(self) -> T {
match self {
ExecutionTypes::Production(t) | ExecutionTypes::Validation(t) => t,
}
}
pub fn split(self) -> (ExecutionKind, T) {
let kind = self.to_kind();
(kind, self.into_inner())
}
}
impl ExecutionKind {
pub fn wrap<T>(self, t: T) -> ExecutionType<T> {
match self {
ExecutionKind::Production => ExecutionTypes::Production(t),
ExecutionKind::Validation => ExecutionTypes::Validation(t),
}
}
}
impl From<Backtrace> for Error {
fn from(e: Backtrace) -> Self {
Error::Backtrace(Box::new(e))
}
}
impl From<KvStoreError> for Error {
fn from(e: KvStoreError) -> Self {
Error::CorruptedBlockState(Box::new(e))
}
}
impl From<crate::db::Error> for Error {
fn from(e: crate::db::Error) -> Self {
Error::CorruptedBlockState(Box::new(e))
}
}
impl<T> core::ops::Deref for ExecutionType<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
ExecutionTypes::Production(p) => p,
ExecutionTypes::Validation(v) => v,
}
}
}
impl<T> core::ops::DerefMut for ExecutionType<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
ExecutionTypes::Production(p) => p,
ExecutionTypes::Validation(v) => v,
}
}
}