use crate::{Input, Output, Transaction, Witness};
use core::hash::Hash;
use fuel_types::{AssetId, BlockHeight};
#[cfg(feature = "std")]
use fuel_types::Bytes32;
#[cfg(feature = "std")]
use fuel_crypto::{Message, Signature};
use itertools::Itertools;
mod error;
use crate::input::coin::{CoinPredicate, CoinSigned};
use crate::input::message::{MessageCoinPredicate, MessageCoinSigned, MessageDataPredicate, MessageDataSigned};
use crate::transaction::consensus_parameters::ConsensusParameters;
use crate::transaction::{field, Executable};
pub use error::CheckError;
impl Input {
#[cfg(feature = "std")]
pub fn check(
&self,
index: usize,
txhash: &Bytes32,
outputs: &[Output],
witnesses: &[Witness],
parameters: &ConsensusParameters,
) -> Result<(), CheckError> {
self.check_without_signature(index, outputs, witnesses, parameters)?;
self.check_signature(index, txhash, witnesses, parameters)?;
Ok(())
}
#[cfg(feature = "std")]
pub fn check_signature(
&self,
index: usize,
txhash: &Bytes32,
witnesses: &[Witness],
parameters: &ConsensusParameters,
) -> Result<(), CheckError> {
match self {
Self::CoinSigned(CoinSigned {
witness_index, owner, ..
})
| Self::MessageCoinSigned(MessageCoinSigned {
witness_index,
recipient: owner,
..
})
| Self::MessageDataSigned(MessageDataSigned {
witness_index,
recipient: owner,
..
}) => {
let witness = witnesses
.get(*witness_index as usize)
.ok_or(CheckError::InputWitnessIndexBounds { index })?
.as_ref();
let bytes = <[u8; Signature::LEN]>::try_from(witness)
.map_err(|_| CheckError::InputInvalidSignature { index })?;
let signature = Signature::from_bytes(bytes);
let message = Message::from_bytes_ref(txhash);
let pk = signature
.recover(message)
.map_err(|_| CheckError::InputInvalidSignature { index })
.map(|pk| Input::owner(&pk))?;
if owner != &pk {
return Err(CheckError::InputInvalidSignature { index });
}
Ok(())
}
Self::CoinPredicate(CoinPredicate { owner, predicate, .. })
| Self::MessageCoinPredicate(MessageCoinPredicate {
recipient: owner,
predicate,
..
})
| Self::MessageDataPredicate(MessageDataPredicate {
recipient: owner,
predicate,
..
}) if !Input::is_predicate_owner_valid(owner, predicate, parameters) => {
Err(CheckError::InputPredicateOwner { index })
}
_ => Ok(()),
}
}
pub fn check_without_signature(
&self,
index: usize,
outputs: &[Output],
witnesses: &[Witness],
parameters: &ConsensusParameters,
) -> Result<(), CheckError> {
match self {
Self::CoinPredicate(CoinPredicate { predicate, .. })
| Self::MessageCoinPredicate(MessageCoinPredicate { predicate, .. })
| Self::MessageDataPredicate(MessageDataPredicate { predicate, .. })
if predicate.is_empty() =>
{
Err(CheckError::InputPredicateEmpty { index })
}
Self::CoinPredicate(CoinPredicate { predicate, .. })
| Self::MessageCoinPredicate(MessageCoinPredicate { predicate, .. })
| Self::MessageDataPredicate(MessageDataPredicate { predicate, .. })
if predicate.len() > parameters.max_predicate_length as usize =>
{
Err(CheckError::InputPredicateLength { index })
}
Self::CoinPredicate(CoinPredicate { predicate_data, .. })
| Self::MessageCoinPredicate(MessageCoinPredicate { predicate_data, .. })
| Self::MessageDataPredicate(MessageDataPredicate { predicate_data, .. })
if predicate_data.len() > parameters.max_predicate_data_length as usize =>
{
Err(CheckError::InputPredicateDataLength { index })
}
Self::CoinSigned(CoinSigned { witness_index, .. })
| Self::MessageCoinSigned(MessageCoinSigned { witness_index, .. })
| Self::MessageDataSigned(MessageDataSigned { witness_index, .. })
if *witness_index as usize >= witnesses.len() =>
{
Err(CheckError::InputWitnessIndexBounds { index })
}
Self::Contract { .. }
if 1 != outputs
.iter()
.filter_map(|output| match output {
Output::Contract { input_index, .. } if *input_index as usize == index => Some(()),
_ => None,
})
.count() =>
{
Err(CheckError::InputContractAssociatedOutputContract { index })
}
Self::MessageDataSigned(MessageDataSigned { data, .. })
| Self::MessageDataPredicate(MessageDataPredicate { data, .. })
if data.is_empty() || data.len() > parameters.max_message_data_length as usize =>
{
Err(CheckError::InputMessageDataLength { index })
}
_ => Ok(()),
}
}
}
impl Output {
pub fn check(&self, index: usize, inputs: &[Input]) -> Result<(), CheckError> {
match self {
Self::Contract { input_index, .. } => match inputs.get(*input_index as usize) {
Some(Input::Contract { .. }) => Ok(()),
_ => Err(CheckError::OutputContractInputIndex { index }),
},
_ => Ok(()),
}
}
}
pub trait FormatValidityChecks {
#[cfg(feature = "std")]
fn check(&self, block_height: BlockHeight, parameters: &ConsensusParameters) -> Result<(), CheckError> {
self.check_without_signatures(block_height, parameters)?;
self.check_signatures(parameters)?;
Ok(())
}
#[cfg(feature = "std")]
fn check_signatures(&self, parameters: &ConsensusParameters) -> Result<(), CheckError>;
fn check_without_signatures(
&self,
block_height: BlockHeight,
parameters: &ConsensusParameters,
) -> Result<(), CheckError>;
}
impl FormatValidityChecks for Transaction {
#[cfg(feature = "std")]
fn check_signatures(&self, parameters: &ConsensusParameters) -> Result<(), CheckError> {
match self {
Transaction::Script(script) => script.check_signatures(parameters),
Transaction::Create(create) => create.check_signatures(parameters),
Transaction::Mint(mint) => mint.check_signatures(parameters),
}
}
fn check_without_signatures(
&self,
block_height: BlockHeight,
parameters: &ConsensusParameters,
) -> Result<(), CheckError> {
match self {
Transaction::Script(script) => script.check_without_signatures(block_height, parameters),
Transaction::Create(create) => create.check_without_signatures(block_height, parameters),
Transaction::Mint(mint) => mint.check_without_signatures(block_height, parameters),
}
}
}
pub(crate) fn check_common_part<T>(
tx: &T,
block_height: BlockHeight,
parameters: &ConsensusParameters,
) -> Result<(), CheckError>
where
T: field::GasPrice + field::GasLimit + field::Maturity + field::Inputs + field::Outputs + field::Witnesses,
{
if tx.gas_limit() > ¶meters.max_gas_per_tx {
Err(CheckError::TransactionGasLimit)?
}
if tx.maturity() > &block_height {
Err(CheckError::TransactionMaturity)?;
}
if tx.inputs().len() > parameters.max_inputs as usize {
Err(CheckError::TransactionInputsMax)?
}
if tx.outputs().len() > parameters.max_outputs as usize {
Err(CheckError::TransactionOutputsMax)?
}
if tx.witnesses().len() > parameters.max_witnesses as usize {
Err(CheckError::TransactionWitnessesMax)?
}
tx.input_asset_ids_unique().try_for_each(|input_asset_id| {
if tx
.outputs()
.iter()
.filter_map(|output| match output {
Output::Change { asset_id, .. } if input_asset_id == asset_id => Some(()),
Output::Change { asset_id, .. } if asset_id != &AssetId::default() && input_asset_id == asset_id => {
Some(())
}
_ => None,
})
.count()
> 1
{
return Err(CheckError::TransactionOutputChangeAssetIdDuplicated(*input_asset_id));
}
Ok(())
})?;
let duplicated_utxo_id = tx
.inputs()
.iter()
.filter_map(|i| i.is_coin().then(|| i.utxo_id()).flatten());
if let Some(utxo_id) = next_duplicate(duplicated_utxo_id).copied() {
return Err(CheckError::DuplicateInputUtxoId { utxo_id });
}
let duplicated_contract_id = tx.inputs().iter().filter_map(Input::contract_id);
if let Some(contract_id) = next_duplicate(duplicated_contract_id).copied() {
return Err(CheckError::DuplicateInputContractId { contract_id });
}
let duplicated_message_id = tx.inputs().iter().filter_map(Input::message_id);
if let Some(message_id) = next_duplicate(duplicated_message_id) {
return Err(CheckError::DuplicateMessageInputId { message_id });
}
tx.inputs().iter().enumerate().try_for_each(|(index, input)| {
input.check_without_signature(index, tx.outputs(), tx.witnesses(), parameters)
})?;
tx.outputs().iter().enumerate().try_for_each(|(index, output)| {
output.check(index, tx.inputs())?;
if let Output::Change { asset_id, .. } = output {
if !tx.input_asset_ids().any(|input_asset_id| input_asset_id == asset_id) {
return Err(CheckError::TransactionOutputChangeAssetIdNotFound(*asset_id));
}
}
if let Output::Coin { asset_id, .. } = output {
if !tx.input_asset_ids().any(|input_asset_id| input_asset_id == asset_id) {
return Err(CheckError::TransactionOutputCoinAssetIdNotFound(*asset_id));
}
}
Ok(())
})?;
Ok(())
}
pub(crate) fn next_duplicate<U>(iter: impl Iterator<Item = U>) -> Option<U>
where
U: PartialEq + Ord + Copy + Hash,
{
#[cfg(not(feature = "std"))]
return iter
.sorted()
.as_slice()
.windows(2)
.filter_map(|u| (u[0] == u[1]).then(|| u[0]))
.next();
#[cfg(feature = "std")]
return iter.duplicates().next();
}