use crate::{
config::BlackList,
error::{
Error,
InputValidationError,
},
ports::{
ChainStateInfoProvider,
GasPriceProvider,
TxPoolPersistentStorage,
WasmChecker,
},
};
use fuel_core_storage::transactional::AtomicView;
use fuel_core_syscall::handlers::log_collector::EcalLogCollector;
use fuel_core_types::{
blockchain::header::ConsensusParametersVersion,
fuel_asm::Word,
fuel_tx::{
ConsensusParameters,
Transaction,
UpgradePurpose,
field::{
MaxFeeLimit,
UpgradePurpose as _,
},
},
fuel_types::BlockHeight,
fuel_vm::{
checked_transaction::{
CheckPredicateParams,
CheckPredicates,
Checked,
CheckedTransaction,
Checks,
IntoChecked,
},
interpreter::{
ExecutableTransaction,
Memory,
},
},
services::{
executor::memory::MemoryPool,
txpool::{
Metadata,
PoolTransaction,
},
},
};
use std::sync::Arc;
#[derive(Clone)]
pub(crate) struct Verification<View> {
pub persistent_storage_provider: Arc<dyn AtomicView<LatestView = View>>,
pub chain_state_info_provider: Arc<dyn ChainStateInfoProvider>,
pub gas_price_provider: Arc<dyn GasPriceProvider>,
pub wasm_checker: Arc<dyn WasmChecker>,
pub memory_pool: MemoryPool,
pub blacklist: BlackList,
}
impl<View> Verification<View>
where
View: TxPoolPersistentStorage,
{
pub fn perform_all_verifications(
&self,
tx: Transaction,
current_height: BlockHeight,
utxo_validation: bool,
allow_syscall: bool,
) -> Result<PoolTransaction, Error> {
let (version, consensus_params) =
self.chain_state_info_provider.latest_consensus_parameters();
let unverified = UnverifiedTx(tx);
let basically_verified_tx =
unverified.perform_basic_verifications(current_height, &consensus_params)?;
let metadata =
calculate_metadata(&basically_verified_tx.0, &consensus_params, version)?;
let gas_price_verified_tx = basically_verified_tx
.perform_gas_price_verifications(
metadata,
self.gas_price_provider.as_ref(),
)?;
let view = self
.persistent_storage_provider
.latest_view()
.map_err(|e| Error::Database(format!("{:?}", e)))?;
let inputs_verified_tx = gas_price_verified_tx
.perform_inputs_verifications(&self.blacklist, metadata)?;
let fully_verified_tx = inputs_verified_tx
.perform_input_computation_verifications(
&consensus_params,
self.wasm_checker.as_ref(),
self.memory_pool.take_raw(),
&view,
utxo_validation,
allow_syscall,
)?;
fully_verified_tx.into_pool_transaction(metadata)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(super) struct UnverifiedTx(Transaction);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(super) struct BasicVerifiedTx(CheckedTransaction);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(super) struct GasPriceVerifiedTx(CheckedTransaction);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(super) struct InputDependenciesVerifiedTx(Checked<Transaction>);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(super) struct FullyVerifiedTx(Checked<Transaction>);
impl UnverifiedTx {
pub fn perform_basic_verifications(
self,
current_height: BlockHeight,
consensus_params: &ConsensusParameters,
) -> Result<BasicVerifiedTx, Error> {
if self.0.is_mint() {
return Err(Error::MintIsDisallowed);
}
let tx = self
.0
.into_checked_basic(current_height, consensus_params)?;
Ok(BasicVerifiedTx(tx.into()))
}
}
impl BasicVerifiedTx {
pub fn perform_gas_price_verifications(
self,
metadata: Metadata,
gas_price_provider: &dyn GasPriceProvider,
) -> Result<GasPriceVerifiedTx, Error> {
let minimal_gas_price = gas_price_provider.next_gas_price();
let max_gas_price = metadata.max_gas_price();
if max_gas_price < minimal_gas_price {
return Err(Error::InsufficientMaxFee {
max_gas_price_from_fee: max_gas_price,
minimal_gas_price,
});
}
Ok(GasPriceVerifiedTx(self.0))
}
}
impl GasPriceVerifiedTx {
pub fn perform_inputs_verifications(
self,
blacklist: &BlackList,
metadata: Metadata,
) -> Result<InputDependenciesVerifiedTx, Error> {
let pool_tx = checked_tx_into_pool(self.0, metadata)?;
if pool_tx.max_gas() == 0 {
return Err(Error::InputValidation(InputValidationError::MaxGasZero))
}
blacklist
.check_blacklisting(&pool_tx)
.map_err(Error::Blacklisted)?;
let checked_transaction: CheckedTransaction = pool_tx.into();
Ok(InputDependenciesVerifiedTx(checked_transaction.into()))
}
}
impl InputDependenciesVerifiedTx {
pub fn perform_input_computation_verifications<View>(
self,
consensus_params: &ConsensusParameters,
wasm_checker: &dyn WasmChecker,
memory: impl Memory,
view: &View,
utxo_validation: bool,
allow_syscall: bool,
) -> Result<FullyVerifiedTx, Error>
where
View: TxPoolPersistentStorage,
{
let mut tx = self.0;
if utxo_validation {
tx = tx.check_signatures(&consensus_params.chain_id())?;
let parameters = CheckPredicateParams::from(consensus_params);
let ecal_handler = EcalLogCollector {
enabled: allow_syscall,
..Default::default()
};
tx = tx.check_predicates(¶meters, memory, view, ecal_handler.clone())?;
ecal_handler
.maybe_print_logs(tracing::info_span!("verification", tx_id = % tx.id()));
debug_assert!(tx.checks().contains(Checks::all()));
}
if let Transaction::Upgrade(upgrade) = tx.transaction()
&& let UpgradePurpose::StateTransition { root } = upgrade.upgrade_purpose()
{
wasm_checker
.validate_uploaded_wasm(root)
.map_err(Error::WasmValidity)?;
}
Ok(FullyVerifiedTx(tx))
}
}
impl FullyVerifiedTx {
pub fn into_pool_transaction(
self,
metadata: Metadata,
) -> Result<PoolTransaction, Error> {
checked_tx_into_pool(self.0.into(), metadata)
}
}
fn calculate_metadata(
tx: &CheckedTransaction,
consensus_params: &ConsensusParameters,
version: ConsensusParametersVersion,
) -> Result<Metadata, Error> {
let metadata = match tx {
CheckedTransaction::Script(tx) => metadata_for_tx(tx, consensus_params, version),
CheckedTransaction::Create(tx) => metadata_for_tx(tx, consensus_params, version),
CheckedTransaction::Mint(_) => {
return Err(Error::MintIsDisallowed);
}
CheckedTransaction::Upgrade(tx) => metadata_for_tx(tx, consensus_params, version),
CheckedTransaction::Upload(tx) => metadata_for_tx(tx, consensus_params, version),
CheckedTransaction::Blob(tx) => metadata_for_tx(tx, consensus_params, version),
};
Ok(metadata)
}
fn metadata_for_tx<Tx>(
tx: &Checked<Tx>,
consensus_params: &ConsensusParameters,
version: ConsensusParametersVersion,
) -> Metadata
where
Tx: ExecutableTransaction,
{
let gas_costs = consensus_params.gas_costs();
let fee_parameters = consensus_params.fee_params();
let max_gas = tx.transaction().max_gas(gas_costs, fee_parameters) as u128;
let gas_price_factor = fee_parameters.gas_price_factor() as u128;
#[cfg(debug_assertions)]
{
use fuel_core_types::fuel_tx::policies::PolicyType;
let policy = tx.transaction().policies().get(PolicyType::MaxFee);
assert!(policy.is_some(), "MaxFee policy is not set");
}
let max_fee = tx.transaction().max_fee_limit() as u128;
let max_gas_price = max_fee
.saturating_mul(gas_price_factor)
.div_ceil(max_gas.max(1));
let metered_bytes_size = tx.transaction().metered_bytes_size();
Metadata::new(
version,
metered_bytes_size,
Word::try_from(max_gas_price).unwrap_or(Word::MAX),
)
}
pub fn checked_tx_into_pool(
tx: CheckedTransaction,
metadata: Metadata,
) -> Result<PoolTransaction, Error> {
match tx {
CheckedTransaction::Script(tx) => Ok(PoolTransaction::Script(tx, metadata)),
CheckedTransaction::Create(tx) => Ok(PoolTransaction::Create(tx, metadata)),
CheckedTransaction::Mint(_) => Err(Error::MintIsDisallowed),
CheckedTransaction::Upgrade(tx) => Ok(PoolTransaction::Upgrade(tx, metadata)),
CheckedTransaction::Upload(tx) => Ok(PoolTransaction::Upload(tx, metadata)),
CheckedTransaction::Blob(tx) => Ok(PoolTransaction::Blob(tx, metadata)),
}
}