5e7086d54c
* eip1559 hard fork activation * eip1559 hard fork activation 2 * added new transaction type for eip1559 * added base fee field to block header * fmt fix * added base fee calculation. added block header validation against base fee * fmt * temporarily added modified transaction pool * tx pool fix of PendingIterator * tx pool fix of UnorderedIterator * tx pool added test for set_scoring * transaction pool changes * added tests for eip1559 transaction and eip1559 receipt * added test for eip1559 transaction execution * block gas limit / block gas target handling * base fee verification moved out of engine * calculate_base_fee moved to EthereumMachine * handling of base_fee_per_gas as part of seal * handling of base_fee_per_gas changed. Different encoding/decoding of block header * eip1559 transaction execution - gas price handling * eip1559 transaction execution - verification, fee burning * effectiveGasPrice removed from the receipt payload (specs) * added support for 1559 txs in tx pool verification * added Aleut test network configuration * effective_tip_scaled replaced by typed_gas_price * eip 3198 - Basefee opcode * rpc - updated structs Block and Header * rpc changes for 1559 * variable renaming according to spec * - typed_gas_price renamed to effective_gas_price - elasticity_multiplier definition moved to update_schedule() * calculate_base_fee simplified * Evm environment context temporary fix for gas limit * fmt fix * fixed fake_sign::sign_call * temporary fix for GASLIMIT opcode to provide gas_target actually * gas_target removed from block header according to spec change: https://github.com/ethereum/EIPs/pull/3566 * tx pool verification fix * env_info base fee changed to Option * fmt fix * pretty format * updated ethereum tests * cache_pending refresh on each update of score * code review fixes * fmt fix * code review fix - changed handling of eip1559_base_fee_max_change_denominator * code review fix - modification.gas_price * Skip gas_limit_bump for Aura * gas_limit calculation changed to target ceil * gas_limit calculation will target ceil on 1559 activation block * transaction verification updated according spec: https://github.com/ethereum/EIPs/pull/3594 * updated json tests * ethereum json tests fix for base_fee
382 lines
13 KiB
Rust
382 lines
13 KiB
Rust
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
//! 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.
|
|
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<U256>,
|
|
/// 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>) -> 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),
|
|
}
|
|
}
|
|
|
|
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<C, S, V> {
|
|
client: C,
|
|
options: Options,
|
|
id: Arc<AtomicUsize>,
|
|
transaction_to_replace: Option<(S, Arc<V>)>,
|
|
}
|
|
|
|
impl<C, S, V> Verifier<C, S, V> {
|
|
/// Creates new transaction verfier with specified options.
|
|
pub fn new(
|
|
client: C,
|
|
options: Options,
|
|
id: Arc<AtomicUsize>,
|
|
transaction_to_replace: Option<(S, Arc<V>)>,
|
|
) -> Self {
|
|
Verifier {
|
|
client,
|
|
options,
|
|
id,
|
|
transaction_to_replace,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<C: Client> txpool::Verifier<Transaction>
|
|
for Verifier<C, ::pool::scoring::NonceAndGasPrice, VerifiedTransaction>
|
|
{
|
|
type Error = transaction::Error;
|
|
type VerifiedTransaction = VerifiedTransaction;
|
|
|
|
fn verify_transaction(
|
|
&self,
|
|
tx: Transaction,
|
|
) -> Result<Self::VerifiedTransaction, Self::Error> {
|
|
// 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 gas_price = tx.effective_gas_price(self.options.block_base_fee);
|
|
// 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 !gas_price.is_zero() && !is_own {
|
|
if gas_price < self.options.minimal_gas_price {
|
|
trace!(
|
|
target: "txqueue",
|
|
"[{:?}] Rejected tx below minimal gas price threshold: {} < {}",
|
|
hash,
|
|
gas_price,
|
|
self.options.minimal_gas_price,
|
|
);
|
|
bail!(transaction::Error::InsufficientGasPrice {
|
|
minimal: self.options.minimal_gas_price,
|
|
got: 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,
|
|
gas_price,
|
|
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(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) => 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 gas_price = transaction.tx().gas_price;
|
|
|
|
if 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,
|
|
gas_price,
|
|
self.options.minimal_gas_price,
|
|
);
|
|
bail!(transaction::Error::InsufficientGasPrice {
|
|
minimal: self.options.minimal_gas_price,
|
|
got: 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),
|
|
})
|
|
}
|
|
}
|