Extract Machine from ethcore (#10949)
* Add client-traits crate Move the BlockInfo trait to new crate * New crate `machine` Contains code extracted from ethcore that defines `Machine`, `Externalities` and other execution related code. * Use new machine and client-traits crates in ethcore * Use new crates machine and client-traits instead of ethcore where appropriate * Fix tests * Don't re-export so many types from ethcore::client * Fixing more fallout from removing re-export * fix test * More fallout from not re-exporting types * Add some docs * cleanup * import the macro edition style * Tweak docs * Add missing import * remove unused ethabi_derive imports * Use latest ethabi-contract
This commit is contained in:
73
ethcore/machine/src/executed.rs
Normal file
73
ethcore/machine/src/executed.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Transaction execution format module.
|
||||
|
||||
use ethereum_types::{U256, Address};
|
||||
use parity_bytes::Bytes;
|
||||
use vm;
|
||||
use trace::{VMTrace, FlatTrace};
|
||||
use common_types::{
|
||||
state_diff::StateDiff,
|
||||
log_entry::LogEntry,
|
||||
errors::ExecutionError,
|
||||
};
|
||||
|
||||
/// Transaction execution receipt.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Executed<T = FlatTrace, V = VMTrace> {
|
||||
/// True if the outer call/create resulted in an exceptional exit.
|
||||
pub exception: Option<vm::Error>,
|
||||
|
||||
/// Gas paid up front for execution of transaction.
|
||||
pub gas: U256,
|
||||
|
||||
/// Gas used during execution of transaction.
|
||||
pub gas_used: U256,
|
||||
|
||||
/// Gas refunded after the execution of transaction.
|
||||
/// To get gas that was required up front, add `refunded` and `gas_used`.
|
||||
pub refunded: U256,
|
||||
|
||||
/// Cumulative gas used in current block so far.
|
||||
///
|
||||
/// `cumulative_gas_used = gas_used(t0) + gas_used(t1) + ... gas_used(tn)`
|
||||
///
|
||||
/// where `tn` is current transaction.
|
||||
pub cumulative_gas_used: U256,
|
||||
|
||||
/// Vector of logs generated by transaction.
|
||||
pub logs: Vec<LogEntry>,
|
||||
|
||||
/// Addresses of contracts created during execution of transaction.
|
||||
/// Ordered from earliest creation.
|
||||
///
|
||||
/// eg. sender creates contract A and A in constructor creates contract B
|
||||
///
|
||||
/// B creation ends first, and it will be the first element of the vector.
|
||||
pub contracts_created: Vec<Address>,
|
||||
/// Transaction output.
|
||||
pub output: Bytes,
|
||||
/// The trace of this transaction.
|
||||
pub trace: Vec<T>,
|
||||
/// The VM trace of this transaction.
|
||||
pub vm_trace: Option<V>,
|
||||
/// The state diff, if we traced it.
|
||||
pub state_diff: Option<StateDiff>,
|
||||
}
|
||||
|
||||
/// Transaction execution result.
|
||||
pub type ExecutionResult = Result<Box<Executed>, ExecutionError>;
|
||||
101
ethcore/machine/src/executed_block.rs
Normal file
101
ethcore/machine/src/executed_block.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! `ExecutedBlock` is the underlying data structure used by other block types to store block
|
||||
//! related info. As a block goes through processing we use different types to signal its state:
|
||||
//! "open", "closed", "locked", "sealed". They all embed an `ExecutedBlock`.
|
||||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use ethereum_types::{H256, U256};
|
||||
|
||||
use account_state::State;
|
||||
use common_types::{
|
||||
header::Header,
|
||||
receipt::Receipt,
|
||||
transaction::SignedTransaction,
|
||||
};
|
||||
use state_db::StateDB;
|
||||
use trace::Tracing;
|
||||
use vm::{EnvInfo, LastHashes};
|
||||
|
||||
/// An internal type for a block's common elements.
|
||||
#[derive(Clone)]
|
||||
pub struct ExecutedBlock {
|
||||
/// Executed block header.
|
||||
pub header: Header,
|
||||
/// Executed transactions.
|
||||
pub transactions: Vec<SignedTransaction>,
|
||||
/// Uncles.
|
||||
pub uncles: Vec<Header>,
|
||||
/// Transaction receipts.
|
||||
pub receipts: Vec<Receipt>,
|
||||
/// Hashes of already executed transactions.
|
||||
pub transactions_set: HashSet<H256>,
|
||||
/// Underlying state.
|
||||
pub state: State<StateDB>,
|
||||
/// Transaction traces.
|
||||
pub traces: Tracing,
|
||||
/// Hashes of last 256 blocks.
|
||||
pub last_hashes: Arc<LastHashes>,
|
||||
}
|
||||
|
||||
impl ExecutedBlock {
|
||||
/// Create a new block from the given `state`.
|
||||
pub fn new(state: State<StateDB>, last_hashes: Arc<LastHashes>, tracing: bool) -> ExecutedBlock {
|
||||
ExecutedBlock {
|
||||
header: Default::default(),
|
||||
transactions: Default::default(),
|
||||
uncles: Default::default(),
|
||||
receipts: Default::default(),
|
||||
transactions_set: Default::default(),
|
||||
state,
|
||||
traces: if tracing {
|
||||
Tracing::enabled()
|
||||
} else {
|
||||
Tracing::Disabled
|
||||
},
|
||||
last_hashes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the environment info concerning this block.
|
||||
pub fn env_info(&self) -> EnvInfo {
|
||||
// TODO: memoise.
|
||||
EnvInfo {
|
||||
number: self.header.number(),
|
||||
author: self.header.author().clone(),
|
||||
timestamp: self.header.timestamp(),
|
||||
difficulty: self.header.difficulty().clone(),
|
||||
last_hashes: self.last_hashes.clone(),
|
||||
gas_used: self.receipts.last().map_or(U256::zero(), |r| r.gas_used),
|
||||
gas_limit: self.header.gas_limit().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get mutable access to a state.
|
||||
pub fn state_mut(&mut self) -> &mut State<StateDB> {
|
||||
&mut self.state
|
||||
}
|
||||
|
||||
/// Get mutable reference to traces.
|
||||
pub fn traces_mut(&mut self) -> &mut Tracing {
|
||||
&mut self.traces
|
||||
}
|
||||
}
|
||||
2281
ethcore/machine/src/executive.rs
Normal file
2281
ethcore/machine/src/executive.rs
Normal file
File diff suppressed because it is too large
Load Diff
670
ethcore/machine/src/externalities.rs
Normal file
670
ethcore/machine/src/externalities.rs
Normal file
@@ -0,0 +1,670 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Transaction Execution environment.
|
||||
|
||||
use std::{cmp, sync::Arc};
|
||||
|
||||
use ethereum_types::{H256, U256, Address, BigEndianHash};
|
||||
use parity_bytes::Bytes;
|
||||
use log::{debug, trace, warn};
|
||||
|
||||
use account_state::{Backend as StateBackend, State, CleanupMode};
|
||||
use common_types::{
|
||||
transaction::UNSIGNED_SENDER,
|
||||
log_entry::LogEntry,
|
||||
};
|
||||
use trace::{Tracer, VMTracer};
|
||||
use vm::{
|
||||
self, ActionParams, ActionValue, EnvInfo, CallType, Schedule,
|
||||
Ext, ContractCreateResult, MessageCallResult, CreateContractAddress,
|
||||
ReturnData, TrapKind
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Machine,
|
||||
substate::Substate,
|
||||
executive::{
|
||||
Executive,
|
||||
contract_address,
|
||||
into_message_call_result,
|
||||
into_contract_create_result,
|
||||
cleanup_mode
|
||||
},
|
||||
};
|
||||
|
||||
/// Policy for handling output data on `RETURN` opcode.
|
||||
pub enum OutputPolicy {
|
||||
/// Return reference to fixed sized output.
|
||||
/// Used for message calls.
|
||||
Return,
|
||||
/// Init new contract as soon as `RETURN` is called.
|
||||
InitContract,
|
||||
}
|
||||
|
||||
/// Transaction properties that externalities need to know about.
|
||||
pub struct OriginInfo {
|
||||
address: Address,
|
||||
origin: Address,
|
||||
gas_price: U256,
|
||||
value: U256,
|
||||
}
|
||||
|
||||
impl OriginInfo {
|
||||
/// Populates origin info from action params.
|
||||
pub fn from(params: &ActionParams) -> Self {
|
||||
OriginInfo {
|
||||
address: params.address.clone(),
|
||||
origin: params.origin.clone(),
|
||||
gas_price: params.gas_price,
|
||||
value: match params.value {
|
||||
ActionValue::Transfer(val) | ActionValue::Apparent(val) => val
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of evm Externalities.
|
||||
pub struct Externalities<'a, T: 'a, V: 'a, B: 'a> {
|
||||
state: &'a mut State<B>,
|
||||
env_info: &'a EnvInfo,
|
||||
depth: usize,
|
||||
stack_depth: usize,
|
||||
origin_info: &'a OriginInfo,
|
||||
substate: &'a mut Substate,
|
||||
machine: &'a Machine,
|
||||
schedule: &'a Schedule,
|
||||
output: OutputPolicy,
|
||||
tracer: &'a mut T,
|
||||
vm_tracer: &'a mut V,
|
||||
static_flag: bool,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, V: 'a, B: 'a> Externalities<'a, T, V, B>
|
||||
where T: Tracer, V: VMTracer, B: StateBackend
|
||||
{
|
||||
/// Basic `Externalities` constructor.
|
||||
pub fn new(
|
||||
state: &'a mut State<B>,
|
||||
env_info: &'a EnvInfo,
|
||||
machine: &'a Machine,
|
||||
schedule: &'a Schedule,
|
||||
depth: usize,
|
||||
stack_depth: usize,
|
||||
origin_info: &'a OriginInfo,
|
||||
substate: &'a mut Substate,
|
||||
output: OutputPolicy,
|
||||
tracer: &'a mut T,
|
||||
vm_tracer: &'a mut V,
|
||||
static_flag: bool,
|
||||
) -> Self {
|
||||
Externalities {
|
||||
state,
|
||||
env_info,
|
||||
depth,
|
||||
stack_depth,
|
||||
origin_info,
|
||||
substate,
|
||||
machine,
|
||||
schedule,
|
||||
output,
|
||||
tracer,
|
||||
vm_tracer,
|
||||
static_flag,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
|
||||
where T: Tracer, V: VMTracer, B: StateBackend
|
||||
{
|
||||
fn initial_storage_at(&self, key: &H256) -> vm::Result<H256> {
|
||||
if self.state.is_base_storage_root_unchanged(&self.origin_info.address)? {
|
||||
self.state.checkpoint_storage_at(0, &self.origin_info.address, key).map(|v| v.unwrap_or_default()).map_err(Into::into)
|
||||
} else {
|
||||
warn!(target: "externalities", "Detected existing account {:#x} where a forced contract creation happened.", self.origin_info.address);
|
||||
Ok(H256::zero())
|
||||
}
|
||||
}
|
||||
|
||||
fn storage_at(&self, key: &H256) -> vm::Result<H256> {
|
||||
self.state.storage_at(&self.origin_info.address, key).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn set_storage(&mut self, key: H256, value: H256) -> vm::Result<()> {
|
||||
if self.static_flag {
|
||||
Err(vm::Error::MutableCallInStaticContext)
|
||||
} else {
|
||||
self.state.set_storage(&self.origin_info.address, key, value).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn exists(&self, address: &Address) -> vm::Result<bool> {
|
||||
self.state.exists(address).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn exists_and_not_null(&self, address: &Address) -> vm::Result<bool> {
|
||||
self.state.exists_and_not_null(address).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn origin_balance(&self) -> vm::Result<U256> {
|
||||
self.balance(&self.origin_info.address).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn balance(&self, address: &Address) -> vm::Result<U256> {
|
||||
self.state.balance(address).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn blockhash(&mut self, number: &U256) -> H256 {
|
||||
if self.env_info.number + 256 >= self.machine.params().eip210_transition {
|
||||
let blockhash_contract_address = self.machine.params().eip210_contract_address;
|
||||
let code_res = self.state.code(&blockhash_contract_address)
|
||||
.and_then(|code| self.state.code_hash(&blockhash_contract_address).map(|hash| (code, hash)))
|
||||
.and_then(|(code, hash)| self.state.code_version(&blockhash_contract_address).map(|version| (code, hash, version)));
|
||||
|
||||
let (code, code_hash, code_version) = match code_res {
|
||||
Ok((code, hash, version)) => (code, hash, version),
|
||||
Err(_) => return H256::zero(),
|
||||
};
|
||||
let data: H256 = BigEndianHash::from_uint(number);
|
||||
|
||||
let params = ActionParams {
|
||||
sender: self.origin_info.address.clone(),
|
||||
address: blockhash_contract_address.clone(),
|
||||
value: ActionValue::Apparent(self.origin_info.value),
|
||||
code_address: blockhash_contract_address.clone(),
|
||||
origin: self.origin_info.origin.clone(),
|
||||
gas: self.machine.params().eip210_contract_gas,
|
||||
gas_price: 0.into(),
|
||||
code,
|
||||
code_hash,
|
||||
code_version,
|
||||
data: Some(data.as_bytes().to_vec()),
|
||||
call_type: CallType::Call,
|
||||
params_type: vm::ParamsType::Separate,
|
||||
};
|
||||
|
||||
let mut ex = Executive::new(self.state, self.env_info, self.machine, self.schedule);
|
||||
let r = ex.call_with_crossbeam(params, self.substate, self.stack_depth + 1, self.tracer, self.vm_tracer);
|
||||
let output = match &r {
|
||||
Ok(ref r) => H256::from_slice(&r.return_data[..32]),
|
||||
_ => H256::zero(),
|
||||
};
|
||||
trace!("ext: blockhash contract({}) -> {:?}({}) self.env_info.number={}\n", number, r, output, self.env_info.number);
|
||||
output
|
||||
} else {
|
||||
// TODO: comment out what this function expects from env_info, since it will produce panics if the latter is inconsistent
|
||||
match *number < U256::from(self.env_info.number) && number.low_u64() >= cmp::max(256, self.env_info.number) - 256 {
|
||||
true => {
|
||||
let index = self.env_info.number - number.low_u64() - 1;
|
||||
assert!(index < self.env_info.last_hashes.len() as u64, format!("Inconsistent env_info, should contain at least {:?} last hashes", index+1));
|
||||
let r = self.env_info.last_hashes[index as usize].clone();
|
||||
trace!("ext: blockhash({}) -> {} self.env_info.number={}\n", number, r, self.env_info.number);
|
||||
r
|
||||
},
|
||||
false => {
|
||||
trace!("ext: blockhash({}) -> null self.env_info.number={}\n", number, self.env_info.number);
|
||||
H256::zero()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create(
|
||||
&mut self,
|
||||
gas: &U256,
|
||||
value: &U256,
|
||||
code: &[u8],
|
||||
parent_version: &U256,
|
||||
address_scheme: CreateContractAddress,
|
||||
trap: bool,
|
||||
) -> ::std::result::Result<ContractCreateResult, TrapKind> {
|
||||
// create new contract address
|
||||
let (address, code_hash) = match self.state.nonce(&self.origin_info.address) {
|
||||
Ok(nonce) => contract_address(address_scheme, &self.origin_info.address, &nonce, &code),
|
||||
Err(e) => {
|
||||
debug!(target: "ext", "Database corruption encountered: {:?}", e);
|
||||
return Ok(ContractCreateResult::Failed)
|
||||
}
|
||||
};
|
||||
|
||||
// prepare the params
|
||||
let params = ActionParams {
|
||||
code_address: address.clone(),
|
||||
address: address.clone(),
|
||||
sender: self.origin_info.address.clone(),
|
||||
origin: self.origin_info.origin.clone(),
|
||||
gas: *gas,
|
||||
gas_price: self.origin_info.gas_price,
|
||||
value: ActionValue::Transfer(*value),
|
||||
code: Some(Arc::new(code.to_vec())),
|
||||
code_hash,
|
||||
code_version: *parent_version,
|
||||
data: None,
|
||||
call_type: CallType::None,
|
||||
params_type: vm::ParamsType::Embedded,
|
||||
};
|
||||
|
||||
if !self.static_flag {
|
||||
if !self.schedule.keep_unsigned_nonce || params.sender != UNSIGNED_SENDER {
|
||||
if let Err(e) = self.state.inc_nonce(&self.origin_info.address) {
|
||||
debug!(target: "ext", "Database corruption encountered: {:?}", e);
|
||||
return Ok(ContractCreateResult::Failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if trap {
|
||||
return Err(TrapKind::Create(params, address));
|
||||
}
|
||||
|
||||
// TODO: handle internal error separately
|
||||
let mut ex = Executive::from_parent(self.state, self.env_info, self.machine, self.schedule, self.depth, self.static_flag);
|
||||
let out = ex.create_with_crossbeam(params, self.substate, self.stack_depth + 1, self.tracer, self.vm_tracer);
|
||||
Ok(into_contract_create_result(out, &address, self.substate))
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
gas: &U256,
|
||||
sender_address: &Address,
|
||||
receive_address: &Address,
|
||||
value: Option<U256>,
|
||||
data: &[u8],
|
||||
code_address: &Address,
|
||||
call_type: CallType,
|
||||
trap: bool,
|
||||
) -> ::std::result::Result<MessageCallResult, TrapKind> {
|
||||
trace!(target: "externalities", "call");
|
||||
|
||||
let code_res = self.state.code(code_address)
|
||||
.and_then(|code| self.state.code_hash(code_address).map(|hash| (code, hash)))
|
||||
.and_then(|(code, hash)| self.state.code_version(code_address).map(|version| (code, hash, version)));
|
||||
|
||||
let (code, code_hash, code_version) = match code_res {
|
||||
Ok((code, hash, version)) => (code, hash, version),
|
||||
Err(_) => return Ok(MessageCallResult::Failed),
|
||||
};
|
||||
|
||||
let mut params = ActionParams {
|
||||
sender: sender_address.clone(),
|
||||
address: receive_address.clone(),
|
||||
value: ActionValue::Apparent(self.origin_info.value),
|
||||
code_address: code_address.clone(),
|
||||
origin: self.origin_info.origin.clone(),
|
||||
gas: *gas,
|
||||
gas_price: self.origin_info.gas_price,
|
||||
code,
|
||||
code_hash,
|
||||
code_version,
|
||||
data: Some(data.to_vec()),
|
||||
call_type,
|
||||
params_type: vm::ParamsType::Separate,
|
||||
};
|
||||
|
||||
if let Some(value) = value {
|
||||
params.value = ActionValue::Transfer(value);
|
||||
}
|
||||
|
||||
if trap {
|
||||
return Err(TrapKind::Call(params));
|
||||
}
|
||||
|
||||
let mut ex = Executive::from_parent(self.state, self.env_info, self.machine, self.schedule, self.depth, self.static_flag);
|
||||
let out = ex.call_with_crossbeam(params, self.substate, self.stack_depth + 1, self.tracer, self.vm_tracer);
|
||||
Ok(into_message_call_result(out))
|
||||
}
|
||||
|
||||
fn extcode(&self, address: &Address) -> vm::Result<Option<Arc<Bytes>>> {
|
||||
Ok(self.state.code(address)?)
|
||||
}
|
||||
|
||||
fn extcodehash(&self, address: &Address) -> vm::Result<Option<H256>> {
|
||||
if self.state.exists_and_not_null(address)? {
|
||||
Ok(self.state.code_hash(address)?)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn extcodesize(&self, address: &Address) -> vm::Result<Option<usize>> {
|
||||
Ok(self.state.code_size(address)?)
|
||||
}
|
||||
|
||||
fn log(&mut self, topics: Vec<H256>, data: &[u8]) -> vm::Result<()> {
|
||||
if self.static_flag {
|
||||
return Err(vm::Error::MutableCallInStaticContext);
|
||||
}
|
||||
|
||||
let address = self.origin_info.address.clone();
|
||||
self.substate.logs.push(LogEntry {
|
||||
address,
|
||||
topics,
|
||||
data: data.to_vec()
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ret(self, gas: &U256, data: &ReturnData, apply_state: bool) -> vm::Result<U256>
|
||||
where Self: Sized {
|
||||
match self.output {
|
||||
OutputPolicy::Return => {
|
||||
Ok(*gas)
|
||||
},
|
||||
OutputPolicy::InitContract if apply_state => {
|
||||
let return_cost = U256::from(data.len()) * U256::from(self.schedule.create_data_gas);
|
||||
if return_cost > *gas || data.len() > self.schedule.create_data_limit {
|
||||
return match self.schedule.exceptional_failed_code_deposit {
|
||||
true => Err(vm::Error::OutOfGas),
|
||||
false => Ok(*gas)
|
||||
}
|
||||
}
|
||||
self.state.init_code(&self.origin_info.address, data.to_vec())?;
|
||||
Ok(*gas - return_cost)
|
||||
},
|
||||
OutputPolicy::InitContract => {
|
||||
Ok(*gas)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn suicide(&mut self, refund_address: &Address) -> vm::Result<()> {
|
||||
if self.static_flag {
|
||||
return Err(vm::Error::MutableCallInStaticContext);
|
||||
}
|
||||
|
||||
let address = self.origin_info.address.clone();
|
||||
let balance = self.balance(&address)?;
|
||||
if &address == refund_address {
|
||||
// TODO [todr] To be consistent with CPP client we set balance to 0 in that case.
|
||||
self.state.sub_balance(&address, &balance, &mut CleanupMode::NoEmpty)?;
|
||||
} else {
|
||||
trace!(target: "ext", "Suiciding {} -> {} (xfer: {})", address, refund_address, balance);
|
||||
self.state.transfer_balance(
|
||||
&address,
|
||||
refund_address,
|
||||
&balance,
|
||||
cleanup_mode(&mut self.substate, &self.schedule)
|
||||
)?;
|
||||
}
|
||||
|
||||
self.tracer.trace_suicide(address, balance, refund_address.clone());
|
||||
self.substate.suicides.insert(address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn schedule(&self) -> &Schedule {
|
||||
&self.schedule
|
||||
}
|
||||
|
||||
fn env_info(&self) -> &EnvInfo {
|
||||
self.env_info
|
||||
}
|
||||
|
||||
fn depth(&self) -> usize {
|
||||
self.depth
|
||||
}
|
||||
|
||||
fn add_sstore_refund(&mut self, value: usize) {
|
||||
self.substate.sstore_clears_refund += value as i128;
|
||||
}
|
||||
|
||||
fn sub_sstore_refund(&mut self, value: usize) {
|
||||
self.substate.sstore_clears_refund -= value as i128;
|
||||
}
|
||||
|
||||
fn trace_next_instruction(&mut self, pc: usize, instruction: u8, current_gas: U256) -> bool {
|
||||
self.vm_tracer.trace_next_instruction(pc, instruction, current_gas)
|
||||
}
|
||||
|
||||
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256, mem_written: Option<(usize, usize)>, store_written: Option<(U256, U256)>) {
|
||||
self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost, mem_written, store_written)
|
||||
}
|
||||
|
||||
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8]) {
|
||||
self.vm_tracer.trace_executed(gas_used, stack_push, mem)
|
||||
}
|
||||
|
||||
fn is_static(&self) -> bool {
|
||||
return self.static_flag
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use ethereum_types::{U256, Address};
|
||||
use evm::{EnvInfo, Ext, CallType};
|
||||
use account_state::State;
|
||||
use ethcore::test_helpers::get_temp_state;
|
||||
use trace::{NoopTracer, NoopVMTracer};
|
||||
|
||||
use crate::{
|
||||
machine::Machine,
|
||||
substate::Substate,
|
||||
test_helpers,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
fn get_test_origin() -> OriginInfo {
|
||||
OriginInfo {
|
||||
address: Address::zero(),
|
||||
origin: Address::zero(),
|
||||
gas_price: U256::zero(),
|
||||
value: U256::zero()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_test_env_info() -> EnvInfo {
|
||||
EnvInfo {
|
||||
number: 100,
|
||||
author: Address::from_low_u64_be(0),
|
||||
timestamp: 0,
|
||||
difficulty: 0.into(),
|
||||
last_hashes: Arc::new(vec![]),
|
||||
gas_used: 0.into(),
|
||||
gas_limit: 0.into(),
|
||||
}
|
||||
}
|
||||
|
||||
struct TestSetup {
|
||||
state: State<::state_db::StateDB>,
|
||||
machine: Machine,
|
||||
schedule: Schedule,
|
||||
sub_state: Substate,
|
||||
env_info: EnvInfo
|
||||
}
|
||||
|
||||
impl Default for TestSetup {
|
||||
fn default() -> Self {
|
||||
TestSetup::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSetup {
|
||||
fn new() -> Self {
|
||||
let machine = test_helpers::load_machine(include_bytes!("../../res/null_morden.json"));
|
||||
let env_info = get_test_env_info();
|
||||
let schedule = machine.schedule(env_info.number);
|
||||
TestSetup {
|
||||
state: get_temp_state(),
|
||||
schedule,
|
||||
machine,
|
||||
sub_state: Substate::new(),
|
||||
env_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_created() {
|
||||
let mut setup = TestSetup::new();
|
||||
let state = &mut setup.state;
|
||||
let mut tracer = NoopTracer;
|
||||
let mut vm_tracer = NoopVMTracer;
|
||||
let origin_info = get_test_origin();
|
||||
|
||||
let ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
|
||||
|
||||
assert_eq!(ext.env_info().number, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_return_block_hash_no_env() {
|
||||
let mut setup = TestSetup::new();
|
||||
let state = &mut setup.state;
|
||||
let mut tracer = NoopTracer;
|
||||
let mut vm_tracer = NoopVMTracer;
|
||||
let origin_info = get_test_origin();
|
||||
|
||||
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
|
||||
|
||||
let hash = ext.blockhash(&"0000000000000000000000000000000000000000000000000000000000120000".parse::<U256>().unwrap());
|
||||
|
||||
assert_eq!(hash, H256::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_return_block_hash() {
|
||||
let test_hash = H256::from_str("afafafafafafafafafafafbcbcbcbcbcbcbcbcbcbeeeeeeeeeeeeedddddddddd").unwrap();
|
||||
let test_env_number = 0x120001;
|
||||
|
||||
let mut setup = TestSetup::new();
|
||||
{
|
||||
let env_info = &mut setup.env_info;
|
||||
env_info.number = test_env_number;
|
||||
let mut last_hashes = (*env_info.last_hashes).clone();
|
||||
last_hashes.push(test_hash.clone());
|
||||
env_info.last_hashes = Arc::new(last_hashes);
|
||||
}
|
||||
let state = &mut setup.state;
|
||||
let mut tracer = NoopTracer;
|
||||
let mut vm_tracer = NoopVMTracer;
|
||||
let origin_info = get_test_origin();
|
||||
|
||||
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
|
||||
|
||||
let hash = ext.blockhash(&"0000000000000000000000000000000000000000000000000000000000120000".parse::<U256>().unwrap());
|
||||
|
||||
assert_eq!(test_hash, hash);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn can_call_fail_empty() {
|
||||
let mut setup = TestSetup::new();
|
||||
let state = &mut setup.state;
|
||||
let mut tracer = NoopTracer;
|
||||
let mut vm_tracer = NoopVMTracer;
|
||||
let origin_info = get_test_origin();
|
||||
|
||||
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
|
||||
|
||||
// this should panic because we have no balance on any account
|
||||
ext.call(
|
||||
&"0000000000000000000000000000000000000000000000000000000000120000".parse::<U256>().unwrap(),
|
||||
&Address::zero(),
|
||||
&Address::zero(),
|
||||
Some("0000000000000000000000000000000000000000000000000000000000150000".parse::<U256>().unwrap()),
|
||||
&[],
|
||||
&Address::zero(),
|
||||
CallType::Call,
|
||||
false,
|
||||
).ok().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_log() {
|
||||
let log_data = vec![120u8, 110u8];
|
||||
let log_topics = vec![H256::from_str("af0fa234a6af46afa23faf23bcbc1c1cb4bcb7bcbe7e7e7ee3ee2edddddddddd").unwrap()];
|
||||
|
||||
let mut setup = TestSetup::new();
|
||||
let state = &mut setup.state;
|
||||
let mut tracer = NoopTracer;
|
||||
let mut vm_tracer = NoopVMTracer;
|
||||
let origin_info = get_test_origin();
|
||||
|
||||
{
|
||||
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
|
||||
ext.log(log_topics, &log_data).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(setup.sub_state.logs.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_suicide() {
|
||||
let refund_account = &Address::zero();
|
||||
|
||||
let mut setup = TestSetup::new();
|
||||
let state = &mut setup.state;
|
||||
let mut tracer = NoopTracer;
|
||||
let mut vm_tracer = NoopVMTracer;
|
||||
let origin_info = get_test_origin();
|
||||
|
||||
{
|
||||
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
|
||||
ext.suicide(refund_account).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(setup.sub_state.suicides.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_create() {
|
||||
use std::str::FromStr;
|
||||
|
||||
let mut setup = TestSetup::new();
|
||||
let state = &mut setup.state;
|
||||
let mut tracer = NoopTracer;
|
||||
let mut vm_tracer = NoopVMTracer;
|
||||
let origin_info = get_test_origin();
|
||||
|
||||
let address = {
|
||||
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
|
||||
match ext.create(&U256::max_value(), &U256::zero(), &[], &U256::zero(), CreateContractAddress::FromSenderAndNonce, false) {
|
||||
Ok(ContractCreateResult::Created(address, _)) => address,
|
||||
_ => panic!("Test create failed; expected Created, got Failed/Reverted."),
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(address, Address::from_str("bd770416a3345f91e4b34576cb804a576fa48eb1").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_create2() {
|
||||
use std::str::FromStr;
|
||||
|
||||
let mut setup = TestSetup::new();
|
||||
let state = &mut setup.state;
|
||||
let mut tracer = NoopTracer;
|
||||
let mut vm_tracer = NoopVMTracer;
|
||||
let origin_info = get_test_origin();
|
||||
|
||||
let address = {
|
||||
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
|
||||
|
||||
match ext.create(&U256::max_value(), &U256::zero(), &[], &U256::zero(), CreateContractAddress::FromSenderSaltAndCodeHash(H256::zero()), false) {
|
||||
Ok(ContractCreateResult::Created(address, _)) => address,
|
||||
_ => panic!("Test create failed; expected Created, got Failed/Reverted."),
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(address, Address::from_str("e33c0c7f7df4809055c3eba6c09cfe4baf1bd9e0").unwrap());
|
||||
}
|
||||
}
|
||||
36
ethcore/machine/src/lib.rs
Normal file
36
ethcore/machine/src/lib.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! This crate provides a state machine and the facilities needed to execute transactions and the
|
||||
//! code contained therein, as well as contract based transaction permissions. All ethereum
|
||||
//! engines embed a `Machine`.
|
||||
|
||||
pub mod executed;
|
||||
pub mod executed_block;
|
||||
pub mod executive;
|
||||
pub mod externalities;
|
||||
pub mod machine;
|
||||
pub mod substate;
|
||||
pub mod transaction_ext;
|
||||
pub mod tx_filter;
|
||||
|
||||
pub use crate::{
|
||||
executed_block::ExecutedBlock,
|
||||
machine::Machine
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
pub mod test_helpers;
|
||||
492
ethcore/machine/src/machine.rs
Normal file
492
ethcore/machine/src/machine.rs
Normal file
@@ -0,0 +1,492 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethereum-like state machine definition.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethereum_types::{U256, H256, Address};
|
||||
use rlp::Rlp;
|
||||
use log::debug;
|
||||
|
||||
use common_types::{
|
||||
BlockNumber,
|
||||
header::Header,
|
||||
engines::{
|
||||
EthashExtensions,
|
||||
params::CommonParams,
|
||||
},
|
||||
errors::{EngineError, EthcoreError as Error},
|
||||
transaction::{self, SYSTEM_ADDRESS, UNSIGNED_SENDER, UnverifiedTransaction, SignedTransaction},
|
||||
};
|
||||
use vm::{CallType, ActionParams, ActionValue, ParamsType};
|
||||
use vm::{EnvInfo, Schedule};
|
||||
|
||||
use account_state::CleanupMode;
|
||||
use client_traits::BlockInfo;
|
||||
use ethcore_builtin::Builtin;
|
||||
use ethcore_call_contract::CallContract;
|
||||
use trace::{NoopTracer, NoopVMTracer};
|
||||
|
||||
use crate::{
|
||||
executed_block::ExecutedBlock,
|
||||
executive::Executive,
|
||||
substate::Substate,
|
||||
tx_filter::TransactionFilter,
|
||||
};
|
||||
|
||||
/// Parity tries to round block.gas_limit to multiple of this constant
|
||||
pub const PARITY_GAS_LIMIT_DETERMINANT: U256 = U256([37, 0, 0, 0]);
|
||||
|
||||
/// Special rules to be applied to the schedule.
|
||||
pub type ScheduleCreationRules = dyn Fn(&mut Schedule, BlockNumber) + Sync + Send;
|
||||
|
||||
/// An ethereum-like state machine.
|
||||
pub struct Machine {
|
||||
params: CommonParams,
|
||||
builtins: Arc<BTreeMap<Address, Builtin>>,
|
||||
tx_filter: Option<Arc<TransactionFilter>>,
|
||||
ethash_extensions: Option<EthashExtensions>,
|
||||
schedule_rules: Option<Box<ScheduleCreationRules>>,
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
/// Regular ethereum machine.
|
||||
pub fn regular(params: CommonParams, builtins: BTreeMap<Address, Builtin>) -> Machine {
|
||||
let tx_filter = TransactionFilter::from_params(¶ms).map(Arc::new);
|
||||
Machine {
|
||||
params,
|
||||
builtins: Arc::new(builtins),
|
||||
tx_filter,
|
||||
ethash_extensions: None,
|
||||
schedule_rules: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Ethereum machine with ethash extensions.
|
||||
// TODO: either unify or specify to mainnet specifically and include other specific-chain HFs?
|
||||
pub fn with_ethash_extensions(params: CommonParams, builtins: BTreeMap<Address, Builtin>, extensions: EthashExtensions) -> Machine {
|
||||
let mut machine = Machine::regular(params, builtins);
|
||||
machine.ethash_extensions = Some(extensions);
|
||||
machine
|
||||
}
|
||||
|
||||
/// Attach special rules to the creation of schedule.
|
||||
pub fn set_schedule_creation_rules(&mut self, rules: Box<ScheduleCreationRules>) {
|
||||
self.schedule_rules = Some(rules);
|
||||
}
|
||||
|
||||
/// Get a reference to the ethash-specific extensions.
|
||||
pub fn ethash_extensions(&self) -> Option<&EthashExtensions> {
|
||||
self.ethash_extensions.as_ref()
|
||||
}
|
||||
|
||||
/// Execute a call as the system address. Block environment information passed to the
|
||||
/// VM is modified to have its gas limit bounded at the upper limit of possible used
|
||||
/// gas, including this system call, capped at the maximum value able to be
|
||||
/// represented by U256. This system call modifies the block state, but discards other
|
||||
/// information. If suicides, logs or refunds happen within the system call, they
|
||||
/// will not be executed or recorded. Gas used by this system call will not be counted
|
||||
/// on the block.
|
||||
pub fn execute_as_system(
|
||||
&self,
|
||||
block: &mut ExecutedBlock,
|
||||
contract_address: Address,
|
||||
gas: U256,
|
||||
data: Option<Vec<u8>>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let (code, code_hash) = {
|
||||
let state = &block.state;
|
||||
|
||||
(state.code(&contract_address)?,
|
||||
state.code_hash(&contract_address)?)
|
||||
};
|
||||
|
||||
self.execute_code_as_system(
|
||||
block,
|
||||
Some(contract_address),
|
||||
code,
|
||||
code_hash,
|
||||
None,
|
||||
gas,
|
||||
data,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Same as execute_as_system, but execute code directly. If contract address is None, use the null sender
|
||||
/// address. If code is None, then this function has no effect. The call is executed without finalization, and does
|
||||
/// not form a transaction.
|
||||
pub fn execute_code_as_system(
|
||||
&self,
|
||||
block: &mut ExecutedBlock,
|
||||
contract_address: Option<Address>,
|
||||
code: Option<Arc<Vec<u8>>>,
|
||||
code_hash: Option<H256>,
|
||||
value: Option<ActionValue>,
|
||||
gas: U256,
|
||||
data: Option<Vec<u8>>,
|
||||
call_type: Option<CallType>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let env_info = {
|
||||
let mut env_info = block.env_info();
|
||||
env_info.gas_limit = env_info.gas_used.saturating_add(gas);
|
||||
env_info
|
||||
};
|
||||
|
||||
let mut state = block.state_mut();
|
||||
|
||||
let params = ActionParams {
|
||||
code_address: contract_address.unwrap_or(UNSIGNED_SENDER),
|
||||
address: contract_address.unwrap_or(UNSIGNED_SENDER),
|
||||
sender: SYSTEM_ADDRESS,
|
||||
origin: SYSTEM_ADDRESS,
|
||||
gas,
|
||||
gas_price: 0.into(),
|
||||
value: value.unwrap_or_else(|| ActionValue::Transfer(0.into())),
|
||||
code,
|
||||
code_hash,
|
||||
code_version: 0.into(),
|
||||
data,
|
||||
call_type: call_type.unwrap_or(CallType::Call),
|
||||
params_type: ParamsType::Separate,
|
||||
};
|
||||
let schedule = self.schedule(env_info.number);
|
||||
let mut ex = Executive::new(&mut state, &env_info, self, &schedule);
|
||||
let mut substate = Substate::new();
|
||||
|
||||
let res = ex.call(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).map_err(|e| EngineError::FailedSystemCall(format!("{}", e)))?;
|
||||
let output = res.return_data.to_vec();
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Push last known block hash to the state.
|
||||
fn push_last_hash(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
|
||||
let params = self.params();
|
||||
if block.header.number() == params.eip210_transition {
|
||||
let state = block.state_mut();
|
||||
state.init_code(¶ms.eip210_contract_address, params.eip210_contract_code.clone())?;
|
||||
}
|
||||
if block.header.number() >= params.eip210_transition {
|
||||
let parent_hash = *block.header.parent_hash();
|
||||
let _ = self.execute_as_system(
|
||||
block,
|
||||
params.eip210_contract_address,
|
||||
params.eip210_contract_gas,
|
||||
Some(parent_hash.as_bytes().to_vec()),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Logic to perform on a new block: updating last hashes and the DAO
|
||||
/// fork, for ethash.
|
||||
pub fn on_new_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
|
||||
self.push_last_hash(block)?;
|
||||
|
||||
if let Some(ref ethash_params) = self.ethash_extensions {
|
||||
if block.header.number() == ethash_params.dao_hardfork_transition {
|
||||
let state = block.state_mut();
|
||||
for child in ðash_params.dao_hardfork_accounts {
|
||||
let beneficiary = ðash_params.dao_hardfork_beneficiary;
|
||||
state.balance(child)
|
||||
.and_then(|b| state.transfer_balance(child, beneficiary, &b, CleanupMode::NoEmpty))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Populate a header's fields based on its parent's header.
|
||||
/// Usually implements the chain scoring rule based on weight.
|
||||
/// The gas floor target must not be lower than the engine's minimum gas limit.
|
||||
pub fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, gas_ceil_target: U256) {
|
||||
header.set_difficulty(parent.difficulty().clone());
|
||||
let gas_limit = parent.gas_limit().clone();
|
||||
assert!(!gas_limit.is_zero(), "Gas limit should be > 0");
|
||||
|
||||
if let Some(ref ethash_params) = self.ethash_extensions {
|
||||
let gas_limit = {
|
||||
let bound_divisor = self.params().gas_limit_bound_divisor;
|
||||
let lower_limit = gas_limit - gas_limit / bound_divisor + 1;
|
||||
let upper_limit = gas_limit + gas_limit / bound_divisor - 1;
|
||||
let gas_limit = if gas_limit < gas_floor_target {
|
||||
let gas_limit = cmp::min(gas_floor_target, upper_limit);
|
||||
round_block_gas_limit(gas_limit, lower_limit, upper_limit)
|
||||
} else if gas_limit > gas_ceil_target {
|
||||
let gas_limit = cmp::max(gas_ceil_target, lower_limit);
|
||||
round_block_gas_limit(gas_limit, lower_limit, upper_limit)
|
||||
} else {
|
||||
let total_lower_limit = cmp::max(lower_limit, gas_floor_target);
|
||||
let total_upper_limit = cmp::min(upper_limit, gas_ceil_target);
|
||||
let gas_limit = cmp::max(gas_floor_target, cmp::min(total_upper_limit,
|
||||
lower_limit + (header.gas_used().clone() * 6u32 / 5) / bound_divisor));
|
||||
round_block_gas_limit(gas_limit, total_lower_limit, total_upper_limit)
|
||||
};
|
||||
// ensure that we are not violating protocol limits
|
||||
debug_assert!(gas_limit >= lower_limit);
|
||||
debug_assert!(gas_limit <= upper_limit);
|
||||
gas_limit
|
||||
};
|
||||
|
||||
header.set_gas_limit(gas_limit);
|
||||
if header.number() >= ethash_params.dao_hardfork_transition &&
|
||||
header.number() <= ethash_params.dao_hardfork_transition + 9 {
|
||||
header.set_extra_data(b"dao-hard-fork"[..].to_owned());
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
header.set_gas_limit({
|
||||
let bound_divisor = self.params().gas_limit_bound_divisor;
|
||||
if gas_limit < gas_floor_target {
|
||||
cmp::min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1)
|
||||
} else {
|
||||
cmp::max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the general parameters of the chain.
|
||||
pub fn params(&self) -> &CommonParams {
|
||||
&self.params
|
||||
}
|
||||
|
||||
/// Get the EVM schedule for the given block number.
|
||||
pub fn schedule(&self, block_number: BlockNumber) -> Schedule {
|
||||
let mut schedule = match self.ethash_extensions {
|
||||
None => self.params.schedule(block_number),
|
||||
Some(ref ext) => {
|
||||
if block_number < ext.homestead_transition {
|
||||
Schedule::new_frontier()
|
||||
} else {
|
||||
self.params.schedule(block_number)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ref rules) = self.schedule_rules {
|
||||
(rules)(&mut schedule, block_number)
|
||||
}
|
||||
|
||||
schedule
|
||||
}
|
||||
|
||||
/// Builtin-contracts for the chain..
|
||||
pub fn builtins(&self) -> &BTreeMap<Address, Builtin> {
|
||||
&*self.builtins
|
||||
}
|
||||
|
||||
/// Attempt to get a handle to a built-in contract.
|
||||
/// Only returns references to activated built-ins.
|
||||
// TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic
|
||||
// from Spec into here and removing the Spec::builtins field.
|
||||
pub fn builtin(&self, a: &Address, block_number: BlockNumber) -> Option<&Builtin> {
|
||||
self.builtins()
|
||||
.get(a)
|
||||
.and_then(|b| if b.is_active(block_number) { Some(b) } else { None })
|
||||
}
|
||||
|
||||
/// Some intrinsic operation parameters; by default they take their value from the `spec()`'s `engine_params`.
|
||||
pub fn maximum_extra_data_size(&self) -> usize { self.params().maximum_extra_data_size }
|
||||
|
||||
/// The nonce with which accounts begin at given block.
|
||||
pub fn account_start_nonce(&self, block: u64) -> U256 {
|
||||
let params = self.params();
|
||||
|
||||
if block >= params.dust_protection_transition {
|
||||
U256::from(params.nonce_cap_increment) * U256::from(block)
|
||||
} else {
|
||||
params.account_start_nonce
|
||||
}
|
||||
}
|
||||
|
||||
/// The network ID that transactions should be signed with.
|
||||
pub fn signing_chain_id(&self, env_info: &EnvInfo) -> Option<u64> {
|
||||
let params = self.params();
|
||||
|
||||
if env_info.number >= params.eip155_transition {
|
||||
Some(params.chain_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Does basic verification of the transaction.
|
||||
pub fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> Result<(), transaction::Error> {
|
||||
let check_low_s = match self.ethash_extensions {
|
||||
Some(ref ext) => header.number() >= ext.homestead_transition,
|
||||
None => true,
|
||||
};
|
||||
|
||||
let chain_id = if header.number() < self.params().validate_chain_id_transition {
|
||||
t.chain_id()
|
||||
} else if header.number() >= self.params().eip155_transition {
|
||||
Some(self.params().chain_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
t.verify_basic(check_low_s, chain_id, false)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Does verification of the transaction against the parent state.
|
||||
pub fn verify_transaction<C: BlockInfo + CallContract>(
|
||||
&self,
|
||||
t: &SignedTransaction,
|
||||
parent: &Header,
|
||||
client: &C
|
||||
) -> Result<(), transaction::Error> {
|
||||
if let Some(ref filter) = self.tx_filter.as_ref() {
|
||||
if !filter.transaction_allowed(&parent.hash(), parent.number() + 1, t, client) {
|
||||
return Err(transaction::Error::NotAllowed.into())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performs pre-validation of RLP decoded transaction before other processing
|
||||
pub fn decode_transaction(&self, transaction: &[u8]) -> Result<UnverifiedTransaction, transaction::Error> {
|
||||
let rlp = Rlp::new(&transaction);
|
||||
if rlp.as_raw().len() > self.params().max_transaction_size {
|
||||
debug!("Rejected oversized transaction of {} bytes", rlp.as_raw().len());
|
||||
return Err(transaction::Error::TooBig)
|
||||
}
|
||||
rlp.as_val().map_err(|e| transaction::Error::InvalidRlp(e.to_string()))
|
||||
}
|
||||
|
||||
/// Get the balance, in base units, associated with an account.
|
||||
/// Extracts data from the live block.
|
||||
pub fn balance(&self, live: &ExecutedBlock, address: &Address) -> Result<U256, Error> {
|
||||
live.state.balance(address).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Increment the balance of an account in the state of the live block.
|
||||
pub fn add_balance(&self, live: &mut ExecutedBlock, address: &Address, amount: &U256) -> Result<(), Error> {
|
||||
live.state_mut().add_balance(address, amount, CleanupMode::NoEmpty).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to round gas_limit a bit so that:
|
||||
// 1) it will still be in desired range
|
||||
// 2) it will be a nearest (with tendency to increase) multiple of PARITY_GAS_LIMIT_DETERMINANT
|
||||
fn round_block_gas_limit(gas_limit: U256, lower_limit: U256, upper_limit: U256) -> U256 {
|
||||
let increased_gas_limit = gas_limit + (PARITY_GAS_LIMIT_DETERMINANT - gas_limit % PARITY_GAS_LIMIT_DETERMINANT);
|
||||
if increased_gas_limit > upper_limit {
|
||||
let decreased_gas_limit = increased_gas_limit - PARITY_GAS_LIMIT_DETERMINANT;
|
||||
if decreased_gas_limit < lower_limit {
|
||||
gas_limit
|
||||
} else {
|
||||
decreased_gas_limit
|
||||
}
|
||||
} else {
|
||||
increased_gas_limit
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
use common_types::header::Header;
|
||||
use super::*;
|
||||
use ethcore::spec;
|
||||
|
||||
fn get_default_ethash_extensions() -> EthashExtensions {
|
||||
EthashExtensions {
|
||||
homestead_transition: 1150000,
|
||||
dao_hardfork_transition: u64::max_value(),
|
||||
dao_hardfork_beneficiary: Address::from_str("0000000000000000000000000000000000000001").unwrap(),
|
||||
dao_hardfork_accounts: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_disallow_unsigned_transactions() {
|
||||
let rlp = "ea80843b9aca0083015f90948921ebb5f79e9e3920abe571004d0b1d5119c154865af3107a400080038080";
|
||||
let transaction: UnverifiedTransaction = ::rlp::decode(&::rustc_hex::FromHex::from_hex(rlp).unwrap()).unwrap();
|
||||
let spec = spec::new_ropsten_test();
|
||||
let ethparams = get_default_ethash_extensions();
|
||||
|
||||
let machine = Machine::with_ethash_extensions(
|
||||
spec.params().clone(),
|
||||
Default::default(),
|
||||
ethparams,
|
||||
);
|
||||
let mut header = Header::new();
|
||||
header.set_number(15);
|
||||
|
||||
let res = machine.verify_transaction_basic(&transaction, &header);
|
||||
assert_eq!(res, Err(transaction::Error::InvalidSignature("Crypto error (Invalid EC signature)".into())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ethash_gas_limit_is_multiple_of_determinant() {
|
||||
use ethereum_types::U256;
|
||||
|
||||
let spec = spec::new_homestead_test();
|
||||
let ethparams = get_default_ethash_extensions();
|
||||
|
||||
let machine = Machine::with_ethash_extensions(
|
||||
spec.params().clone(),
|
||||
Default::default(),
|
||||
ethparams,
|
||||
);
|
||||
|
||||
let mut parent = Header::new();
|
||||
let mut header = Header::new();
|
||||
header.set_number(1);
|
||||
|
||||
// this test will work for this constant only
|
||||
assert_eq!(PARITY_GAS_LIMIT_DETERMINANT, U256::from(37));
|
||||
|
||||
// when parent.gas_limit < gas_floor_target:
|
||||
parent.set_gas_limit(U256::from(50_000));
|
||||
machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
|
||||
assert_eq!(*header.gas_limit(), U256::from(50_024));
|
||||
|
||||
// when parent.gas_limit > gas_ceil_target:
|
||||
parent.set_gas_limit(U256::from(250_000));
|
||||
machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
|
||||
assert_eq!(*header.gas_limit(), U256::from(249_787));
|
||||
|
||||
// when parent.gas_limit is in miner's range
|
||||
header.set_gas_used(U256::from(150_000));
|
||||
parent.set_gas_limit(U256::from(150_000));
|
||||
machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
|
||||
assert_eq!(*header.gas_limit(), U256::from(150_035));
|
||||
|
||||
// when parent.gas_limit is in miner's range
|
||||
// && we can NOT increase it to be multiple of constant
|
||||
header.set_gas_used(U256::from(150_000));
|
||||
parent.set_gas_limit(U256::from(150_000));
|
||||
machine.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(150_002));
|
||||
assert_eq!(*header.gas_limit(), U256::from(149_998));
|
||||
|
||||
// when parent.gas_limit is in miner's range
|
||||
// && we can NOT increase it to be multiple of constant
|
||||
// && we can NOT decrease it to be multiple of constant
|
||||
header.set_gas_used(U256::from(150_000));
|
||||
parent.set_gas_limit(U256::from(150_000));
|
||||
machine.populate_from_parent(&mut header, &parent, U256::from(150_000), U256::from(150_002));
|
||||
assert_eq!(*header.gas_limit(), U256::from(150_002));
|
||||
}
|
||||
}
|
||||
97
ethcore/machine/src/substate.rs
Normal file
97
ethcore/machine/src/substate.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Execution environment substate.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use ethereum_types::Address;
|
||||
use common_types::log_entry::LogEntry;
|
||||
|
||||
/// State changes which should be applied in finalize,
|
||||
/// after transaction is fully executed.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Substate {
|
||||
/// Any accounts that have suicided.
|
||||
pub suicides: HashSet<Address>,
|
||||
|
||||
/// Any accounts that are touched.
|
||||
pub touched: HashSet<Address>,
|
||||
|
||||
/// Any logs.
|
||||
pub logs: Vec<LogEntry>,
|
||||
|
||||
/// Refund counter of SSTORE.
|
||||
pub sstore_clears_refund: i128,
|
||||
|
||||
/// Created contracts.
|
||||
pub contracts_created: Vec<Address>,
|
||||
}
|
||||
|
||||
impl Substate {
|
||||
/// Creates new substate.
|
||||
pub fn new() -> Self {
|
||||
Substate::default()
|
||||
}
|
||||
|
||||
/// Merge secondary substate `s` into self, accruing each element correspondingly.
|
||||
pub fn accrue(&mut self, s: Substate) {
|
||||
self.suicides.extend(s.suicides);
|
||||
self.touched.extend(s.touched);
|
||||
self.logs.extend(s.logs);
|
||||
self.sstore_clears_refund += s.sstore_clears_refund;
|
||||
self.contracts_created.extend(s.contracts_created);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethereum_types::Address;
|
||||
use common_types::log_entry::LogEntry;
|
||||
use super::Substate;
|
||||
|
||||
#[test]
|
||||
fn created() {
|
||||
let sub_state = Substate::new();
|
||||
assert_eq!(sub_state.suicides.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accrue() {
|
||||
let mut sub_state = Substate::new();
|
||||
sub_state.contracts_created.push(Address::from_low_u64_be(1));
|
||||
sub_state.logs.push(LogEntry {
|
||||
address: Address::from_low_u64_be(1),
|
||||
topics: vec![],
|
||||
data: vec![]
|
||||
});
|
||||
sub_state.sstore_clears_refund = (15000 * 5).into();
|
||||
sub_state.suicides.insert(Address::from_low_u64_be(10));
|
||||
|
||||
let mut sub_state_2 = Substate::new();
|
||||
sub_state_2.contracts_created.push(Address::from_low_u64_be(2u64));
|
||||
sub_state_2.logs.push(LogEntry {
|
||||
address: Address::from_low_u64_be(1),
|
||||
topics: vec![],
|
||||
data: vec![]
|
||||
});
|
||||
sub_state_2.sstore_clears_refund = (15000 * 7).into();
|
||||
|
||||
sub_state.accrue(sub_state_2);
|
||||
assert_eq!(sub_state.contracts_created.len(), 2);
|
||||
assert_eq!(sub_state.sstore_clears_refund, (15000 * 12).into());
|
||||
assert_eq!(sub_state.suicides.len(), 1);
|
||||
}
|
||||
}
|
||||
60
ethcore/machine/src/test_helpers.rs
Normal file
60
ethcore/machine/src/test_helpers.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Provide facilities to create `Machine` instances for testing various networks.
|
||||
|
||||
use common_types::engines::params::CommonParams;
|
||||
use ethjson;
|
||||
use crate::Machine;
|
||||
|
||||
|
||||
pub fn load_machine(reader: &[u8]) -> Machine {
|
||||
let spec = ethjson::spec::Spec::load(reader).expect("chain spec is invalid");
|
||||
|
||||
let builtins = spec.accounts.builtins().into_iter().map(|p| (p.0.into(), From::from(p.1))).collect();
|
||||
let params = CommonParams::from(spec.params);
|
||||
|
||||
if let ethjson::spec::Engine::Ethash(ref ethash) = spec.engine {
|
||||
Machine::with_ethash_extensions(params, builtins, ethash.params.clone().into())
|
||||
} else {
|
||||
Machine::regular(params, builtins)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead.
|
||||
pub fn new_frontier_test_machine() -> Machine { load_machine(include_bytes!("../../res/ethereum/frontier_test.json")) }
|
||||
|
||||
/// Create a new Foundation Homestead-era chain spec as though it never changed from Frontier.
|
||||
pub fn new_homestead_test_machine() -> Machine { load_machine(include_bytes!("../../res/ethereum/homestead_test.json")) }
|
||||
|
||||
/// Create a new Foundation Homestead-EIP210-era chain spec as though it never changed from Homestead/Frontier.
|
||||
pub fn new_eip210_test_machine() -> Machine { load_machine(include_bytes!("../../res/ethereum/eip210_test.json")) }
|
||||
|
||||
/// Create a new Foundation Byzantium era spec.
|
||||
pub fn new_byzantium_test_machine() -> Machine { load_machine(include_bytes!("../../res/ethereum/byzantium_test.json")) }
|
||||
|
||||
/// Create a new Foundation Constantinople era spec.
|
||||
pub fn new_constantinople_test_machine() -> Machine { load_machine(include_bytes!("../../res/ethereum/constantinople_test.json")) }
|
||||
|
||||
/// Create a new Foundation St. Peter's (Contantinople Fix) era spec.
|
||||
pub fn new_constantinople_fix_test_machine() -> Machine { load_machine(include_bytes!("../../res/ethereum/st_peters_test.json")) }
|
||||
|
||||
/// Create a new Musicoin-MCIP3-era spec.
|
||||
pub fn new_mcip3_test_machine() -> Machine { load_machine(include_bytes!("../../res/ethereum/mcip3_test.json")) }
|
||||
|
||||
/// Create new Kovan spec with wasm activated at certain block
|
||||
pub fn new_kovan_wasm_test_machine() -> Machine { load_machine(include_bytes!("../../res/ethereum/kovan_wasm_test.json")) }
|
||||
43
ethcore/machine/src/transaction_ext.rs
Normal file
43
ethcore/machine/src/transaction_ext.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethereum transaction
|
||||
|
||||
use evm::Schedule;
|
||||
use common_types::transaction::{self, Action};
|
||||
|
||||
/// Extends transaction with gas verification method.
|
||||
pub trait Transaction {
|
||||
/// Get the transaction cost in gas for this transaction.
|
||||
fn gas_required(&self, schedule: &Schedule) -> u64;
|
||||
}
|
||||
|
||||
impl Transaction for transaction::Transaction {
|
||||
fn gas_required(&self, schedule: &Schedule) -> u64 {
|
||||
gas_required_for(match self.action {
|
||||
Action::Create => true,
|
||||
Action::Call(_) => false
|
||||
}, &self.data, schedule)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the transaction cost in gas for the given params.
|
||||
fn gas_required_for(is_create: bool, data: &[u8], schedule: &Schedule) -> u64 {
|
||||
data.iter().fold(
|
||||
(if is_create {schedule.tx_create_gas} else {schedule.tx_gas}) as u64,
|
||||
|g, b| g + (match *b { 0 => schedule.tx_data_zero_gas, _ => schedule.tx_data_non_zero_gas }) as u64
|
||||
)
|
||||
}
|
||||
301
ethcore/machine/src/tx_filter.rs
Normal file
301
ethcore/machine/src/tx_filter.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Smart contract based transaction filter.
|
||||
|
||||
use ethabi::FunctionOutputDecoder;
|
||||
use ethabi_contract::use_contract;
|
||||
use ethereum_types::{H256, U256, Address};
|
||||
use log::{trace, error};
|
||||
use lru_cache::LruCache;
|
||||
|
||||
use ethcore_call_contract::CallContract;
|
||||
use client_traits::BlockInfo;
|
||||
use parking_lot::Mutex;
|
||||
use common_types::{
|
||||
BlockNumber,
|
||||
ids::BlockId,
|
||||
engines::params::CommonParams,
|
||||
transaction::{Action, SignedTransaction}
|
||||
};
|
||||
use keccak_hash::KECCAK_EMPTY;
|
||||
|
||||
use_contract!(transact_acl_deprecated, "res/tx_acl_deprecated.json");
|
||||
use_contract!(transact_acl, "res/tx_acl.json");
|
||||
|
||||
const MAX_CACHE_SIZE: usize = 4096;
|
||||
|
||||
mod tx_permissions {
|
||||
pub const _ALL: u32 = 0xffffffff;
|
||||
pub const NONE: u32 = 0x0;
|
||||
pub const BASIC: u32 = 0b00000001;
|
||||
pub const CALL: u32 = 0b00000010;
|
||||
pub const CREATE: u32 = 0b00000100;
|
||||
pub const _PRIVATE: u32 = 0b00001000;
|
||||
}
|
||||
|
||||
/// Connection filter that uses a contract to manage permissions.
|
||||
pub struct TransactionFilter {
|
||||
contract_address: Address,
|
||||
transition_block: BlockNumber,
|
||||
permission_cache: Mutex<LruCache<(H256, Address), u32>>,
|
||||
contract_version_cache: Mutex<LruCache<(H256), Option<U256>>>
|
||||
}
|
||||
|
||||
impl TransactionFilter {
|
||||
/// Create a new instance if address is specified in params.
|
||||
pub fn from_params(params: &CommonParams) -> Option<TransactionFilter> {
|
||||
params.transaction_permission_contract.map(|address|
|
||||
TransactionFilter {
|
||||
contract_address: address,
|
||||
transition_block: params.transaction_permission_contract_transition,
|
||||
permission_cache: Mutex::new(LruCache::new(MAX_CACHE_SIZE)),
|
||||
contract_version_cache: Mutex::new(LruCache::new(MAX_CACHE_SIZE)),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Check if transaction is allowed at given block.
|
||||
pub fn transaction_allowed<C: BlockInfo + CallContract>(&self, parent_hash: &H256, block_number: BlockNumber, transaction: &SignedTransaction, client: &C) -> bool {
|
||||
if block_number < self.transition_block { return true; }
|
||||
|
||||
let mut permission_cache = self.permission_cache.lock();
|
||||
let mut contract_version_cache = self.contract_version_cache.lock();
|
||||
|
||||
let (tx_type, to) = match transaction.action {
|
||||
Action::Create => (tx_permissions::CREATE, Address::zero()),
|
||||
Action::Call(address) => if client.code_hash(&address, BlockId::Hash(*parent_hash)).map_or(false, |c| c != KECCAK_EMPTY) {
|
||||
(tx_permissions::CALL, address)
|
||||
} else {
|
||||
(tx_permissions::BASIC, address)
|
||||
}
|
||||
};
|
||||
|
||||
let sender = transaction.sender();
|
||||
let value = transaction.value;
|
||||
let key = (*parent_hash, sender);
|
||||
|
||||
if let Some(permissions) = permission_cache.get_mut(&key) {
|
||||
return *permissions & tx_type != 0;
|
||||
}
|
||||
|
||||
let contract_address = self.contract_address;
|
||||
let contract_version = contract_version_cache.get_mut(parent_hash).and_then(|v| *v).or_else(|| {
|
||||
let (data, decoder) = transact_acl::functions::contract_version::call();
|
||||
decoder.decode(&client.call_contract(BlockId::Hash(*parent_hash), contract_address, data).ok()?).ok()
|
||||
});
|
||||
contract_version_cache.insert(*parent_hash, contract_version);
|
||||
|
||||
// Check permissions in smart contract based on its version
|
||||
let (permissions, filter_only_sender) = match contract_version {
|
||||
Some(version) => {
|
||||
let version_u64 = version.low_u64();
|
||||
trace!(target: "tx_filter", "Version of tx permission contract: {}", version);
|
||||
match version_u64 {
|
||||
2 => {
|
||||
let (data, decoder) = transact_acl::functions::allowed_tx_types::call(sender, to, value);
|
||||
client.call_contract(BlockId::Hash(*parent_hash), contract_address, data)
|
||||
.and_then(|value| decoder.decode(&value).map_err(|e| e.to_string()))
|
||||
.map(|(p, f)| (p.low_u32(), f))
|
||||
.unwrap_or_else(|e| {
|
||||
error!(target: "tx_filter", "Error calling tx permissions contract: {:?}", e);
|
||||
(tx_permissions::NONE, true)
|
||||
})
|
||||
},
|
||||
_ => {
|
||||
error!(target: "tx_filter", "Unknown version of tx permissions contract is used");
|
||||
(tx_permissions::NONE, true)
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
trace!(target: "tx_filter", "Fallback to the deprecated version of tx permission contract");
|
||||
let (data, decoder) = transact_acl_deprecated::functions::allowed_tx_types::call(sender);
|
||||
(client.call_contract(BlockId::Hash(*parent_hash), contract_address, data)
|
||||
.and_then(|value| decoder.decode(&value).map_err(|e| e.to_string()))
|
||||
.map(|p| p.low_u32())
|
||||
.unwrap_or_else(|e| {
|
||||
error!(target: "tx_filter", "Error calling tx permissions contract: {:?}", e);
|
||||
tx_permissions::NONE
|
||||
}), true)
|
||||
}
|
||||
};
|
||||
|
||||
if filter_only_sender {
|
||||
permission_cache.insert((*parent_hash, sender), permissions);
|
||||
}
|
||||
trace!(target: "tx_filter",
|
||||
"Given transaction data: sender: {:?} to: {:?} value: {}. Permissions required: {:X}, got: {:X}",
|
||||
sender, to, value, tx_type, permissions
|
||||
);
|
||||
permissions & tx_type != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use tempdir::TempDir;
|
||||
use ethereum_types::{U256, Address};
|
||||
|
||||
use common_types::{
|
||||
ids::BlockId,
|
||||
transaction::{Transaction, Action}
|
||||
};
|
||||
use ethcore::{
|
||||
client::{BlockChainClient, Client, ClientConfig},
|
||||
spec::Spec,
|
||||
miner::Miner,
|
||||
test_helpers,
|
||||
};
|
||||
use ethkey::{Secret, KeyPair};
|
||||
use ethcore_io::IoChannel;
|
||||
|
||||
use super::TransactionFilter;
|
||||
|
||||
/// Contract code: https://gist.github.com/VladLupashevskyi/84f18eabb1e4afadf572cf92af3e7e7f
|
||||
#[test]
|
||||
fn transaction_filter() {
|
||||
let spec_data = include_str!("../../res/tx_permission_tests/contract_ver_2_genesis.json");
|
||||
|
||||
let db = test_helpers::new_db();
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let spec = Spec::load(&tempdir.path(), spec_data.as_bytes()).unwrap();
|
||||
|
||||
let client = Client::new(
|
||||
ClientConfig::default(),
|
||||
&spec,
|
||||
db,
|
||||
Arc::new(Miner::new_for_tests(&spec, None)),
|
||||
IoChannel::disconnected(),
|
||||
).unwrap();
|
||||
let key1 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000001")).unwrap();
|
||||
let key2 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000002")).unwrap();
|
||||
let key3 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000003")).unwrap();
|
||||
let key4 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000004")).unwrap();
|
||||
let key5 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000005")).unwrap();
|
||||
let key6 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000006")).unwrap();
|
||||
let key7 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000007")).unwrap();
|
||||
|
||||
let filter = TransactionFilter::from_params(spec.params()).unwrap();
|
||||
let mut basic_tx = Transaction::default();
|
||||
basic_tx.action = Action::Call(Address::from_str("d41c057fd1c78805aac12b0a94a405c0461a6fbb").unwrap());
|
||||
let create_tx = Transaction::default();
|
||||
let mut call_tx = Transaction::default();
|
||||
call_tx.action = Action::Call(Address::from_str("0000000000000000000000000000000000000005").unwrap());
|
||||
|
||||
let mut basic_tx_with_ether_and_to_key7 = Transaction::default();
|
||||
basic_tx_with_ether_and_to_key7.action = Action::Call(Address::from_str("d41c057fd1c78805aac12b0a94a405c0461a6fbb").unwrap());
|
||||
basic_tx_with_ether_and_to_key7.value = U256::from(123123);
|
||||
let mut call_tx_with_ether = Transaction::default();
|
||||
call_tx_with_ether.action = Action::Call(Address::from_str("0000000000000000000000000000000000000005").unwrap());
|
||||
call_tx_with_ether.value = U256::from(123123);
|
||||
|
||||
let mut basic_tx_to_key6 = Transaction::default();
|
||||
basic_tx_to_key6.action = Action::Call(Address::from_str("e57bfe9f44b819898f47bf37e5af72a0783e1141").unwrap());
|
||||
let mut basic_tx_with_ether_and_to_key6 = Transaction::default();
|
||||
basic_tx_with_ether_and_to_key6.action = Action::Call(Address::from_str("e57bfe9f44b819898f47bf37e5af72a0783e1141").unwrap());
|
||||
basic_tx_with_ether_and_to_key6.value = U256::from(123123);
|
||||
|
||||
let genesis = client.block_hash(BlockId::Latest).unwrap();
|
||||
let block_number = 1;
|
||||
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key2.secret(), None), &*client));
|
||||
// same tx but request is allowed because the contract only enables at block #1
|
||||
assert!(filter.transaction_allowed(&genesis, 0, &create_tx.clone().sign(key2.secret(), None), &*client));
|
||||
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key1.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key1.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key1.secret(), None), &*client));
|
||||
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key2.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key2.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key2.secret(), None), &*client));
|
||||
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key3.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key3.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key3.secret(), None), &*client));
|
||||
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key4.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key4.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key4.secret(), None), &*client));
|
||||
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key1.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key1.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key1.secret(), None), &*client));
|
||||
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &basic_tx_with_ether_and_to_key7.clone().sign(key5.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &call_tx_with_ether.clone().sign(key5.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key6.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx_with_ether_and_to_key7.clone().sign(key6.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx_to_key6.clone().sign(key7.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &basic_tx_with_ether_and_to_key6.clone().sign(key7.secret(), None), &*client));
|
||||
}
|
||||
|
||||
/// Contract code: https://gist.github.com/arkpar/38a87cb50165b7e683585eec71acb05a
|
||||
#[test]
|
||||
fn transaction_filter_deprecated() {
|
||||
let spec_data = include_str!("../../res/tx_permission_tests/deprecated_contract_genesis.json");
|
||||
|
||||
let db = test_helpers::new_db();
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let spec = Spec::load(&tempdir.path(), spec_data.as_bytes()).unwrap();
|
||||
|
||||
let client = Client::new(
|
||||
ClientConfig::default(),
|
||||
&spec,
|
||||
db,
|
||||
Arc::new(Miner::new_for_tests(&spec, None)),
|
||||
IoChannel::disconnected(),
|
||||
).unwrap();
|
||||
let key1 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000001")).unwrap();
|
||||
let key2 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000002")).unwrap();
|
||||
let key3 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000003")).unwrap();
|
||||
let key4 = KeyPair::from_secret(Secret::from("0000000000000000000000000000000000000000000000000000000000000004")).unwrap();
|
||||
|
||||
let filter = TransactionFilter::from_params(spec.params()).unwrap();
|
||||
let mut basic_tx = Transaction::default();
|
||||
basic_tx.action = Action::Call(Address::from_str("0000000000000000000000000000000000000032").unwrap());
|
||||
let create_tx = Transaction::default();
|
||||
let mut call_tx = Transaction::default();
|
||||
call_tx.action = Action::Call(Address::from_str("0000000000000000000000000000000000000005").unwrap());
|
||||
|
||||
let genesis = client.block_hash(BlockId::Latest).unwrap();
|
||||
let block_number = 1;
|
||||
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key2.secret(), None), &*client));
|
||||
// same tx but request is allowed because the contract only enables at block #1
|
||||
assert!(filter.transaction_allowed(&genesis, 0, &create_tx.clone().sign(key2.secret(), None), &*client));
|
||||
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key1.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key1.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key1.secret(), None), &*client));
|
||||
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key2.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key2.secret(), None), &*client));
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key2.secret(), None), &*client));
|
||||
|
||||
assert!(filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key3.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key3.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key3.secret(), None), &*client));
|
||||
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &basic_tx.clone().sign(key4.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &create_tx.clone().sign(key4.secret(), None), &*client));
|
||||
assert!(!filter.transaction_allowed(&genesis, block_number, &call_tx.clone().sign(key4.secret(), None), &*client));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user