// Copyright 2015-2018 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity 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. // Parity 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 Parity. 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; use std::sync::Arc; use std::sync::atomic::{self, AtomicUsize}; use ethereum_types::{U256, H256}; use rlp::Encodable; use transaction; use txpool; use super::client::{Client, TransactionType}; use super::VerifiedTransaction; /// Verification options. #[derive(Debug, Clone, PartialEq)] pub struct Options { /// Minimal allowed gas price. pub minimal_gas_price: U256, /// Current block gas limit. pub block_gas_limit: U256, /// Maximal gas limit for a single transaction. pub tx_gas_limit: U256, } #[cfg(test)] impl Default for Options { fn default() -> Self { Options { minimal_gas_price: 0.into(), block_gas_limit: U256::max_value(), tx_gas_limit: U256::max_value(), } } } /// 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 pub fn gas_price(&self) -> &U256 { match *self { Transaction::Unverified(ref tx) => &tx.gas_price, Transaction::Retracted(ref tx) => &tx.gas_price, Transaction::Local(ref tx) => &tx.gas_price, } } fn gas(&self) -> &U256 { match *self { Transaction::Unverified(ref tx) => &tx.gas, Transaction::Retracted(ref tx) => &tx.gas, Transaction::Local(ref tx) => &tx.gas, } } fn transaction(&self) -> &transaction::Transaction { 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()); 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(); // 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 !tx.gas_price().is_zero() && !is_own { if tx.gas_price() < &self.options.minimal_gas_price { trace!( target: "txqueue", "[{:?}] Rejected tx below minimal gas price threshold: {} < {}", hash, tx.gas_price(), self.options.minimal_gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: self.options.minimal_gas_price, got: *tx.gas_price(), }); } 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.gas_price(), vtx.transaction.gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: vtx.transaction.gas_price, got: *tx.gas_price(), }); } } } // 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) => tx, }; // Verify RLP payload if let Err(err) = self.client.decode_transaction(&transaction.rlp_bytes()) { debug!(target: "txqueue", "[{:?}] Rejected transaction's rlp payload", err); bail!(err) } let sender = transaction.sender(); let account_details = self.client.account_details(&sender); if transaction.gas_price < 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, transaction.gas_price, self.options.minimal_gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: self.options.minimal_gas_price, got: transaction.gas_price, }); } } let cost = transaction.value + transaction.gas_price * transaction.gas; 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.nonce < account_details.nonce { debug!( target: "txqueue", "[{:?}] Rejected tx with old nonce ({} < {})", hash, transaction.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), }) } }