use crate::{
Input,
Output,
Transaction,
Witness,
};
use core::hash::Hash;
use fuel_types::{
AssetId,
BlockHeight,
};
#[cfg(feature = "std")]
use fuel_types::{
Address,
Bytes32,
ChainId,
};
use itertools::Itertools;
#[cfg(feature = "std")]
use std::collections::HashMap;
mod error;
use crate::{
input::{
coin::{
CoinPredicate,
CoinSigned,
},
message::{
MessageCoinPredicate,
MessageCoinSigned,
MessageDataPredicate,
MessageDataSigned,
},
},
transaction::{
consensus_parameters::ConsensusParameters,
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,
recovery_cache: &mut Option<HashMap<u8, Address>>,
) -> Result<(), CheckError> {
self.check_without_signature(index, outputs, witnesses, parameters)?;
self.check_signature(
index,
txhash,
witnesses,
¶meters.chain_id,
recovery_cache,
)?;
Ok(())
}
#[cfg(feature = "std")]
pub fn check_signature(
&self,
index: usize,
txhash: &Bytes32,
witnesses: &[Witness],
chain_id: &ChainId,
recovery_cache: &mut Option<HashMap<u8, Address>>,
) -> 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 recover_address = || -> Result<Address, CheckError> {
let witness = witnesses
.get(*witness_index as usize)
.ok_or(CheckError::InputWitnessIndexBounds { index })?;
witness.recover_witness(txhash, *witness_index as usize)
};
let recovered_address = if let Some(cache) = recovery_cache {
if let Some(recovered_address) = cache.get(witness_index) {
*recovered_address
} else {
let recovered_address = recover_address()?;
cache.insert(*witness_index, recovered_address);
recovered_address
}
} else {
recover_address()?
};
if owner != &recovered_address {
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, chain_id) => {
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(¶meters.chain_id)?;
Ok(())
}
#[cfg(feature = "std")]
fn check_signatures(&self, chain_id: &ChainId) -> 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, chain_id: &ChainId) -> Result<(), CheckError> {
match self {
Transaction::Script(script) => script.check_signatures(chain_id),
Transaction::Create(create) => create.check_signatures(chain_id),
Transaction::Mint(mint) => mint.check_signatures(chain_id),
}
}
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)?
}
let any_spendable_input = tx.inputs().iter().find(|input| match input {
Input::CoinSigned(_)
| Input::CoinPredicate(_)
| Input::MessageCoinSigned(_)
| Input::MessageCoinPredicate(_) => true,
Input::MessageDataSigned(_)
| Input::MessageDataPredicate(_)
| Input::Contract(_) => false,
});
if any_spendable_input.is_none() {
Err(CheckError::NoSpendableInput)?
}
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"))]
{
iter.sorted()
.as_slice()
.windows(2)
.filter_map(|u| (u[0] == u[1]).then(|| u[0]))
.next()
}
#[cfg(feature = "std")]
{
iter.duplicates().next()
}
}