openethereum/crates/ethcore/src/client/evm_test_client.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

409 lines
14 KiB
Rust
Raw Normal View History

2020-09-22 14:53:52 +02:00
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of OpenEthereum.
2020-09-22 14:53:52 +02:00
// 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.
2020-09-22 14:53:52 +02:00
// 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
2020-09-22 14:53:52 +02:00
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
//! Simple Client used for EVM tests.
use client;
use db;
use ethereum_types::{H160, H256, U256};
use ethtrie;
use evm::{FinalizationResult, VMType};
use executive;
use factory::{self, Factories};
use journaldb;
use kvdb::{self, KeyValueDB};
use pod_state;
use spec;
use state;
use state_db;
use std::{fmt, sync::Arc};
use trace;
use trie;
use types::{log_entry, receipt, transaction};
use vm::{self, ActionParams};
/// EVM test Error.
#[derive(Debug)]
pub enum EvmTestError {
/// Trie integrity error.
Trie(Box<ethtrie::TrieError>),
/// EVM error.
Evm(vm::Error),
/// Initialization error.
ClientError(::error::Error),
/// Post-condition failure,
PostCondition(String),
}
impl<E: Into<::error::Error>> From<E> for EvmTestError {
fn from(err: E) -> Self {
EvmTestError::ClientError(err.into())
}
}
impl fmt::Display for EvmTestError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use self::EvmTestError::*;
2020-08-05 06:08:03 +02:00
match *self {
Trie(ref err) => write!(fmt, "Trie: {}", err),
Evm(ref err) => write!(fmt, "EVM: {}", err),
ClientError(ref err) => write!(fmt, "{}", err),
PostCondition(ref err) => write!(fmt, "{}", err),
}
}
}
use ethereum::{self};
use ethjson::spec::ForkSpec;
/// Simplified, single-block EVM test client.
pub struct EvmTestClient<'a> {
state: state::State<state_db::StateDB>,
spec: &'a spec::Spec,
dump_state: fn(&state::State<state_db::StateDB>) -> Option<pod_state::PodState>,
}
fn no_dump_state(_: &state::State<state_db::StateDB>) -> Option<pod_state::PodState> {
None
}
impl<'a> fmt::Debug for EvmTestClient<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("EvmTestClient")
.field("state", &self.state)
.field("spec", &self.spec.name)
.finish()
}
}
impl<'a> EvmTestClient<'a> {
/// Converts a json spec definition into spec.
pub fn spec_from_json(spec: &ForkSpec) -> Option<spec::Spec> {
match *spec {
ForkSpec::Frontier => Some(ethereum::new_frontier_test()),
ForkSpec::Homestead => Some(ethereum::new_homestead_test()),
ForkSpec::EIP150 => Some(ethereum::new_eip150_test()),
ForkSpec::EIP158 => Some(ethereum::new_eip161_test()),
ForkSpec::Byzantium => Some(ethereum::new_byzantium_test()),
ForkSpec::Constantinople => Some(ethereum::new_constantinople_test()),
ForkSpec::ConstantinopleFix => Some(ethereum::new_constantinople_fix_test()),
ForkSpec::Istanbul => Some(ethereum::new_istanbul_test()),
ForkSpec::EIP158ToByzantiumAt5 => Some(ethereum::new_transition_test()),
2020-09-10 08:04:14 +02:00
ForkSpec::ByzantiumToConstantinopleFixAt5 => {
Some(ethereum::new_byzantium_to_constantinoplefixat5_test())
}
ForkSpec::Berlin => Some(ethereum::new_berlin_test()),
Sunce86/eip 1559 (#393) * 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
2021-06-04 12:12:24 +02:00
ForkSpec::London => Some(ethereum::new_london_test()),
ForkSpec::FrontierToHomesteadAt5
| ForkSpec::HomesteadToDaoAt5
2020-09-10 08:04:14 +02:00
| ForkSpec::HomesteadToEIP150At5
| ForkSpec::ByzantiumToConstantinopleAt5 => None,
2020-08-05 06:08:03 +02:00
}
}
2020-08-05 06:08:03 +02:00
/// Change default function for dump state (default does not dump)
pub fn set_dump_state_fn(
&mut self,
dump_state: fn(&state::State<state_db::StateDB>) -> Option<pod_state::PodState>,
) {
self.dump_state = dump_state;
}
2020-08-05 06:08:03 +02:00
/// Creates new EVM test client with in-memory DB initialized with genesis of given Spec.
/// Takes a `TrieSpec` to set the type of trie.
pub fn new_with_trie(
spec: &'a spec::Spec,
trie_spec: trie::TrieSpec,
) -> Result<Self, EvmTestError> {
let factories = Self::factories(trie_spec);
let state = Self::state_from_spec(spec, &factories)?;
2020-08-05 06:08:03 +02:00
Ok(EvmTestClient {
state,
spec,
dump_state: no_dump_state,
})
}
2020-08-05 06:08:03 +02:00
/// Creates new EVM test client with an in-memory DB initialized with genesis of given chain Spec.
pub fn new(spec: &'a spec::Spec) -> Result<Self, EvmTestError> {
Self::new_with_trie(spec, trie::TrieSpec::Secure)
}
2020-08-05 06:08:03 +02:00
/// Creates new EVM test client with an in-memory DB initialized with given PodState.
/// Takes a `TrieSpec` to set the type of trie.
pub fn from_pod_state_with_trie(
spec: &'a spec::Spec,
pod_state: pod_state::PodState,
trie_spec: trie::TrieSpec,
) -> Result<Self, EvmTestError> {
let factories = Self::factories(trie_spec);
let state = Self::state_from_pod(spec, &factories, pod_state)?;
2020-08-05 06:08:03 +02:00
Ok(EvmTestClient {
state,
spec,
dump_state: no_dump_state,
})
}
2020-08-05 06:08:03 +02:00
/// Creates new EVM test client with an in-memory DB initialized with given PodState.
pub fn from_pod_state(
spec: &'a spec::Spec,
pod_state: pod_state::PodState,
) -> Result<Self, EvmTestError> {
Self::from_pod_state_with_trie(spec, pod_state, trie::TrieSpec::Secure)
}
2020-08-05 06:08:03 +02:00
fn factories(trie_spec: trie::TrieSpec) -> Factories {
Factories {
vm: factory::VmFactory::new(VMType::Interpreter, 5 * 1024),
trie: trie::TrieFactory::new(trie_spec),
accountdb: Default::default(),
}
2020-08-05 06:08:03 +02:00
}
fn state_from_spec(
spec: &'a spec::Spec,
factories: &Factories,
) -> Result<state::State<state_db::StateDB>, EvmTestError> {
let db = Arc::new(ethcore_db::InMemoryWithMetrics::create(
db::NUM_COLUMNS.expect("We use column-based DB; qed"),
));
let journal_db =
journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE);
let mut state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024);
state_db = spec.ensure_db_good(state_db, factories)?;
2020-08-05 06:08:03 +02:00
let genesis = spec.genesis_header();
// Write DB
{
let mut batch = kvdb::DBTransaction::new();
state_db.journal_under(&mut batch, 0, &genesis.hash())?;
2018-07-02 11:04:48 +02:00
db.write(batch)?;
}
2020-08-05 06:08:03 +02:00
state::State::from_existing(
state_db,
*genesis.state_root(),
spec.engine.account_start_nonce(0),
factories.clone(),
)
.map_err(EvmTestError::Trie)
}
2020-08-05 06:08:03 +02:00
fn state_from_pod(
spec: &'a spec::Spec,
factories: &Factories,
pod_state: pod_state::PodState,
) -> Result<state::State<state_db::StateDB>, EvmTestError> {
let db = Arc::new(ethcore_db::InMemoryWithMetrics::create(
db::NUM_COLUMNS.expect("We use column-based DB; qed"),
));
let journal_db =
journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE);
let state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024);
let mut state = state::State::new(
state_db,
spec.engine.account_start_nonce(0),
factories.clone(),
);
state.populate_from(pod_state);
state.commit()?;
Ok(state)
}
2020-08-05 06:08:03 +02:00
/// Return current state.
pub fn state(&self) -> &state::State<state_db::StateDB> {
&self.state
}
2020-08-05 06:08:03 +02:00
/// Execute the VM given ActionParams and tracer.
/// Returns amount of gas left and the output.
pub fn call<T: trace::Tracer, V: trace::VMTracer>(
&mut self,
params: ActionParams,
tracer: &mut T,
vm_tracer: &mut V,
) -> Result<FinalizationResult, EvmTestError> {
let genesis = self.spec.genesis_header();
let info = client::EnvInfo {
number: genesis.number(),
author: *genesis.author(),
timestamp: genesis.timestamp(),
difficulty: *genesis.difficulty(),
2017-09-04 16:36:49 +02:00
last_hashes: Arc::new([H256::default(); 256].to_vec()),
gas_used: 0.into(),
gas_limit: *genesis.gas_limit(),
Sunce86/eip 1559 (#393) * 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
2021-06-04 12:12:24 +02:00
base_fee: genesis.base_fee(),
};
self.call_envinfo(params, tracer, vm_tracer, info)
}
2020-08-05 06:08:03 +02:00
/// Execute the VM given envinfo, ActionParams and tracer.
/// Returns amount of gas left and the output.
pub fn call_envinfo<T: trace::Tracer, V: trace::VMTracer>(
&mut self,
params: ActionParams,
tracer: &mut T,
vm_tracer: &mut V,
info: client::EnvInfo,
) -> Result<FinalizationResult, EvmTestError> {
let mut substate = state::Substate::new();
let machine = self.spec.engine.machine();
let schedule = machine.schedule(info.number);
let mut executive = executive::Executive::new(&mut self.state, &info, &machine, &schedule);
executive
.call(params, &mut substate, tracer, vm_tracer)
.map_err(EvmTestError::Evm)
}
2020-08-05 06:08:03 +02:00
/// Executes a SignedTransaction within context of the provided state and `EnvInfo`.
/// Returns the state root, gas left and the output.
pub fn transact<T: trace::Tracer, V: trace::VMTracer>(
&mut self,
env_info: &client::EnvInfo,
transaction: transaction::SignedTransaction,
tracer: T,
vm_tracer: V,
) -> std::result::Result<TransactSuccess<T::Output, V::Output>, TransactErr> {
let initial_gas = transaction.tx().gas;
// Verify transaction
let is_ok = transaction.verify_basic(true, None);
if let Err(error) = is_ok {
return Err(TransactErr {
state_root: *self.state.root(),
error: error.into(),
end_state: (self.dump_state)(&self.state),
});
}
2020-08-05 06:08:03 +02:00
// Apply transaction
let result = self.state.apply_with_tracing(
&env_info,
self.spec.engine.machine(),
&transaction,
tracer,
vm_tracer,
);
let scheme = self
.spec
.engine
.machine()
.create_address_scheme(env_info.number);
2020-08-05 06:08:03 +02:00
// Touch the coinbase at the end of the test to simulate
// miner reward.
2020-09-22 14:53:52 +02:00
// Details: https://github.com/openethereum/openethereum/issues/9431
let schedule = self.spec.engine.machine().schedule(env_info.number);
self.state
.add_balance(
&env_info.author,
&0.into(),
if schedule.no_empty {
state::CleanupMode::NoEmpty
} else {
state::CleanupMode::ForceCreate
},
2020-08-05 06:08:03 +02:00
)
.ok();
// Touching also means that we should remove the account if it's within eip161
// conditions.
self.state
.kill_garbage(
&vec![env_info.author].into_iter().collect(),
schedule.kill_empty,
&None,
false,
)
.ok();
2020-08-05 06:08:03 +02:00
self.state.commit().ok();
2020-08-05 06:08:03 +02:00
let state_root = *self.state.root();
2020-08-05 06:08:03 +02:00
let end_state = (self.dump_state)(&self.state);
2020-08-05 06:08:03 +02:00
match result {
Ok(result) => Ok(TransactSuccess {
state_root,
gas_left: initial_gas - result.receipt.gas_used,
outcome: result.receipt.outcome.clone(),
output: result.output,
trace: result.trace,
2017-10-20 15:40:25 +02:00
vm_trace: result.vm_trace,
logs: result.receipt.logs.clone(),
contract_address: if let transaction::Action::Create = transaction.tx().action {
Some(
executive::contract_address(
scheme,
&transaction.sender(),
&transaction.tx().nonce,
&transaction.tx().data,
)
.0,
2020-08-05 06:08:03 +02:00
)
} else {
None
},
end_state,
}),
Err(error) => Err(TransactErr {
state_root,
error,
end_state,
}),
}
}
}
/// To be returned inside a std::result::Result::Ok after a successful
/// transaction completed.
#[allow(dead_code)]
pub struct TransactSuccess<T, V> {
/// State root
pub state_root: H256,
/// Amount of gas left
pub gas_left: U256,
/// Output
pub output: Vec<u8>,
/// Traces
pub trace: Vec<T>,
/// VM Traces
pub vm_trace: Option<V>,
/// Created contract address (if any)
pub contract_address: Option<H160>,
/// Generated logs
pub logs: Vec<log_entry::LogEntry>,
/// outcome
pub outcome: receipt::TransactionOutcome,
/// end state if needed
pub end_state: Option<pod_state::PodState>,
}
/// To be returned inside a std::result::Result::Err after a failed
/// transaction.
#[allow(dead_code)]
pub struct TransactErr {
/// State root
pub state_root: H256,
/// Execution error
pub error: ::error::Error,
/// end state if needed
pub end_state: Option<pod_state::PodState>,
}