// Copyright 2015-2020 Parity Technologies (UK) Ltd. // This file is part of OpenEthereum. // OpenEthereum is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // OpenEthereum is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with OpenEthereum. If not, see . //! Transaction Verifier //! //! Responsible for verifying a transaction before importing to the pool. //! Should make sure that the transaction is structuraly valid. //! //! May have some overlap with `Readiness` since we don't want to keep around //! stalled transactions. use std::{ cmp, sync::{ atomic::{self, AtomicUsize}, Arc, }, }; use ethereum_types::{H256, U256}; use txpool; use types::transaction; use super::{ client::{Client, TransactionType}, VerifiedTransaction, }; /// Verification options. #[derive(Debug, Clone, PartialEq)] pub struct Options { /// Minimal allowed gas price (actually minimal block producer reward = effective_priority_fee). pub minimal_gas_price: U256, /// Current block gas limit. pub block_gas_limit: U256, /// Block base fee. Exists if the EIP 1559 is activated. pub block_base_fee: Option, /// Maximal gas limit for a single transaction. pub tx_gas_limit: U256, /// Skip checks for early rejection, to make sure that local transactions are always imported. pub no_early_reject: bool, } #[cfg(test)] impl Default for Options { fn default() -> Self { Options { minimal_gas_price: 0.into(), block_gas_limit: U256::max_value(), block_base_fee: None, tx_gas_limit: U256::max_value(), no_early_reject: false, } } } /// Transaction to verify. #[cfg_attr(test, derive(Clone))] pub enum Transaction { /// Fresh, never verified transaction. /// /// We need to do full verification of such transactions Unverified(transaction::UnverifiedTransaction), /// Transaction from retracted block. /// /// We could skip some parts of verification of such transactions Retracted(transaction::UnverifiedTransaction), /// Locally signed or retracted transaction. /// /// We can skip consistency verifications and just verify readiness. Local(transaction::PendingTransaction), } impl Transaction { /// Return transaction hash pub fn hash(&self) -> H256 { match *self { Transaction::Unverified(ref tx) => tx.hash(), Transaction::Retracted(ref tx) => tx.hash(), Transaction::Local(ref tx) => tx.hash(), } } /// Return transaction gas price for non 1559 transactions or maxFeePerGas for 1559 transactions. pub fn gas_price(&self) -> &U256 { match *self { Transaction::Unverified(ref tx) => &tx.tx().gas_price, Transaction::Retracted(ref tx) => &tx.tx().gas_price, Transaction::Local(ref tx) => &tx.tx().gas_price, } } fn gas(&self) -> &U256 { match *self { Transaction::Unverified(ref tx) => &tx.tx().gas, Transaction::Retracted(ref tx) => &tx.tx().gas, Transaction::Local(ref tx) => &tx.tx().gas, } } /// Return actual gas price of the transaction depending on the current block base fee pub fn effective_gas_price(&self, block_base_fee: Option) -> U256 { match *self { Transaction::Unverified(ref tx) => tx.effective_gas_price(block_base_fee), Transaction::Retracted(ref tx) => tx.effective_gas_price(block_base_fee), Transaction::Local(ref tx) => tx.effective_gas_price(block_base_fee), } } /// Return effective fee - part of the transaction fee that goes to the miner pub fn effective_priority_fee(&self, block_base_fee: Option) -> U256 { match *self { Transaction::Unverified(ref tx) => tx.effective_priority_fee(block_base_fee), Transaction::Retracted(ref tx) => tx.effective_priority_fee(block_base_fee), Transaction::Local(ref tx) => tx.effective_priority_fee(block_base_fee), } } /// Cheeck if transaction is service transaction pub fn is_service(&self) -> bool { match *self { Transaction::Unverified(ref tx) => tx.is_service(), Transaction::Retracted(ref tx) => tx.is_service(), Transaction::Local(ref tx) => tx.is_service(), } } fn transaction(&self) -> &transaction::TypedTransaction { match *self { Transaction::Unverified(ref tx) => &*tx, Transaction::Retracted(ref tx) => &*tx, Transaction::Local(ref tx) => &*tx, } } fn is_local(&self) -> bool { match *self { Transaction::Local(..) => true, _ => false, } } fn is_retracted(&self) -> bool { match *self { Transaction::Retracted(..) => true, _ => false, } } } /// Transaction verifier. /// /// Verification can be run in parallel for all incoming transactions. #[derive(Debug)] pub struct Verifier { client: C, options: Options, id: Arc, transaction_to_replace: Option<(S, Arc)>, } impl Verifier { /// Creates new transaction verfier with specified options. pub fn new( client: C, options: Options, id: Arc, transaction_to_replace: Option<(S, Arc)>, ) -> Self { Verifier { client, options, id, transaction_to_replace, } } } impl txpool::Verifier for Verifier { type Error = transaction::Error; type VerifiedTransaction = VerifiedTransaction; fn verify_transaction( &self, tx: Transaction, ) -> Result { // The checks here should be ordered by cost/complexity. // Cheap checks should be done as early as possible to discard unneeded transactions early. let hash = tx.hash(); if self.client.transaction_already_included(&hash) { trace!(target: "txqueue", "[{:?}] Rejected tx already in the blockchain", hash); bail!(transaction::Error::AlreadyImported) } let gas_limit = cmp::min(self.options.tx_gas_limit, self.options.block_gas_limit); if tx.gas() > &gas_limit { debug!( target: "txqueue", "[{:?}] Rejected transaction above gas limit: {} > min({}, {})", hash, tx.gas(), self.options.block_gas_limit, self.options.tx_gas_limit, ); bail!(transaction::Error::GasLimitExceeded { limit: gas_limit, got: *tx.gas(), }); } let minimal_gas = self.client.required_gas(tx.transaction().tx()); if tx.gas() < &minimal_gas { trace!(target: "txqueue", "[{:?}] Rejected transaction with insufficient gas: {} < {}", hash, tx.gas(), minimal_gas, ); bail!(transaction::Error::InsufficientGas { minimal: minimal_gas, got: *tx.gas(), }) } let is_own = tx.is_local(); let is_service = tx.is_service(); // Quick exit for non-service and non-local transactions // // We're checking if the transaction is below configured minimal gas price // or the effective minimal gas price in case the pool is full. if !is_service && !is_own { let effective_priority_fee = tx.effective_priority_fee(self.options.block_base_fee); if effective_priority_fee < self.options.minimal_gas_price { trace!( target: "txqueue", "[{:?}] Rejected tx below minimal gas price threshold: {} < {}", hash, effective_priority_fee, self.options.minimal_gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: self.options.minimal_gas_price, got: effective_priority_fee, }); } if let Some((ref scoring, ref vtx)) = self.transaction_to_replace { if scoring.should_reject_early(vtx, &tx) { trace!( target: "txqueue", "[{:?}] Rejected tx early, cause it doesn't have any chance to get to the pool: (gas price: {} < {})", hash, tx.effective_gas_price(self.options.block_base_fee), vtx.transaction.effective_gas_price(self.options.block_base_fee), ); return Err(transaction::Error::TooCheapToReplace { prev: Some( vtx.transaction .effective_gas_price(self.options.block_base_fee), ), new: Some(tx.effective_gas_price(self.options.block_base_fee)), }); } } } // Some more heavy checks below. // Actually recover sender and verify that transaction let is_retracted = tx.is_retracted(); let transaction = match tx { Transaction::Retracted(tx) | Transaction::Unverified(tx) => { match self.client.verify_transaction(tx) { Ok(signed) => signed.into(), Err(err) => { debug!(target: "txqueue", "[{:?}] Rejected tx {:?}", hash, err); bail!(err) } } } Transaction::Local(tx) => match self.client.verify_transaction_basic(&**tx) { Ok(()) => tx, Err(err) => { warn!(target: "txqueue", "[{:?}] Rejected local tx {:?}", hash, err); return Err(err); } }, }; // Verify RLP payload if let Err(err) = self.client.decode_transaction(&transaction.encode()) { debug!(target: "txqueue", "[{:?}] Rejected transaction's rlp payload", err); bail!(err) } let sender = transaction.sender(); let account_details = self.client.account_details(&sender); let effective_priority_fee = transaction.effective_priority_fee(self.options.block_base_fee); if effective_priority_fee < self.options.minimal_gas_price { let transaction_type = self.client.transaction_type(&transaction); if let TransactionType::Service = transaction_type { debug!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); } else if is_own || account_details.is_local { info!(target: "own_tx", "Local tx {:?} below minimal gas price accepted", hash); } else { trace!( target: "txqueue", "[{:?}] Rejected tx below minimal gas price threshold: {} < {}", hash, effective_priority_fee, self.options.minimal_gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: self.options.minimal_gas_price, got: effective_priority_fee, }); } } let gas_price = transaction.tx().gas_price; if gas_price < transaction.max_priority_fee_per_gas() { bail!(transaction::Error::InsufficientGasPrice { minimal: transaction.max_priority_fee_per_gas(), got: gas_price, }); } let (full_gas_price, overflow_1) = gas_price.overflowing_mul(transaction.tx().gas); let (cost, overflow_2) = transaction.tx().value.overflowing_add(full_gas_price); if overflow_1 || overflow_2 { trace!( target: "txqueue", "[{:?}] Rejected tx, price overflow", hash ); bail!(transaction::Error::InsufficientBalance { cost: U256::max_value(), balance: account_details.balance, }); } if account_details.balance < cost { debug!( target: "txqueue", "[{:?}] Rejected tx with not enough balance: {} < {}", hash, account_details.balance, cost, ); bail!(transaction::Error::InsufficientBalance { cost: cost, balance: account_details.balance, }); } if transaction.tx().nonce < account_details.nonce { debug!( target: "txqueue", "[{:?}] Rejected tx with old nonce ({} < {})", hash, transaction.tx().nonce, account_details.nonce, ); bail!(transaction::Error::Old); } let priority = match (is_own || account_details.is_local, is_retracted) { (true, _) => super::Priority::Local, (false, false) => super::Priority::Regular, (false, true) => super::Priority::Retracted, }; Ok(VerifiedTransaction { transaction, priority, hash, sender, insertion_id: self.id.fetch_add(1, atomic::Ordering::AcqRel), }) } }