use std::collections::BTreeMap;
use std::cmp;
use std::sync::Arc;
use vapory_types::{U256, H256, Address};
use tetsy_rlp::Rlp;
use log::debug;
use common_types::{
BlockNumber,
header::Header,
engines::{
VapashExtensions,
params::CommonParams,
},
errors::{EngineError, VapcoreError as Error},
transaction::{self, SYSTEM_ADDRESS, UNSIGNED_SENDER, UnverifiedTransaction, SignedTransaction},
};
use tetsy_vm::{ActionType, ActionParams, ActionValue, ParamsType};
use tetsy_vm::{EnvInfo, Schedule};
use account_state::CleanupMode;
use client_traits::BlockInfo;
use vapcore_builtin::Builtin;
use vapcore_call_contract::CallContract;
use vapcore_trace::{NoopTracer, NoopVMTracer};
use crate::{
executed_block::ExecutedBlock,
executive::Executive,
substate::Substate,
tx_filter::TransactionFilter,
};
pub const TETSY_GAS_LIMIT_DETERMINANT: U256 = U256([37, 0, 0, 0]);
pub type ScheduleCreationRules = dyn Fn(&mut Schedule, BlockNumber) + Sync + Send;
pub struct Machine {
params: CommonParams,
builtins: Arc<BTreeMap<Address, Builtin>>,
tx_filter: Option<Arc<TransactionFilter>>,
vapash_extensions: Option<VapashExtensions>,
schedule_rules: Option<Box<ScheduleCreationRules>>,
}
impl 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,
vapash_extensions: None,
schedule_rules: None,
}
}
pub fn with_vapash_extensions(params: CommonParams, builtins: BTreeMap<Address, Builtin>, extensions: VapashExtensions) -> Machine {
let mut mashina = Machine::regular(params, builtins);
mashina.vapash_extensions = Some(extensions);
mashina
}
pub fn set_schedule_creation_rules(&mut self, rules: Box<ScheduleCreationRules>) {
self.schedule_rules = Some(rules);
}
pub fn vapash_extensions(&self) -> Option<&VapashExtensions> {
self.vapash_extensions.as_ref()
}
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,
)
}
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>>,
action_type: Option<ActionType>,
) -> 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,
action_type: action_type.unwrap_or(ActionType::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)
}
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(())
}
pub fn on_new_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
self.push_last_hash(block)?;
if let Some(ref vapash_params) = self.vapash_extensions {
if block.header.number() == vapash_params.dao_hardfork_transition {
let state = block.state_mut();
for child in &vapash_params.dao_hardfork_accounts {
let beneficiary = &vapash_params.dao_hardfork_beneficiary;
state.balance(child)
.and_then(|b| state.transfer_balance(child, beneficiary, &b, CleanupMode::NoEmpty))?;
}
}
}
Ok(())
}
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 vapash_params) = self.vapash_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)
};
debug_assert!(gas_limit >= lower_limit);
debug_assert!(gas_limit <= upper_limit);
gas_limit
};
header.set_gas_limit(gas_limit);
if header.number() >= vapash_params.dao_hardfork_transition &&
header.number() <= vapash_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)
}
});
}
pub fn params(&self) -> &CommonParams {
&self.params
}
pub fn schedule(&self, block_number: BlockNumber) -> Schedule {
let mut schedule = match self.vapash_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
}
pub fn builtins(&self) -> &BTreeMap<Address, Builtin> {
&*self.builtins
}
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 })
}
pub fn maximum_extra_data_size(&self) -> usize { self.params().maximum_extra_data_size }
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
}
}
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
}
}
pub fn verify_transaction_basic(&self, t: &UnverifiedTransaction, header: &Header) -> Result<(), transaction::Error> {
let check_low_s = match self.vapash_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)?;
Ok(())
}
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(())
}
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()))
}
pub fn balance(&self, live: &ExecutedBlock, address: &Address) -> Result<U256, Error> {
live.state.balance(address).map_err(Into::into)
}
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)
}
}
fn round_block_gas_limit(gas_limit: U256, lower_limit: U256, upper_limit: U256) -> U256 {
let increased_gas_limit = gas_limit + (TETSY_GAS_LIMIT_DETERMINANT - gas_limit % TETSY_GAS_LIMIT_DETERMINANT);
if increased_gas_limit > upper_limit {
let decreased_gas_limit = increased_gas_limit - TETSY_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 vapcore_spec;
fn get_default_vapash_extensions() -> VapashExtensions {
VapashExtensions {
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 = ::tetsy_rlp::decode(&::rustc_hex::FromHex::from_hex(rlp).unwrap()).unwrap();
let spec = vapcore_spec::new_ropsten_test();
let vapparams = get_default_vapash_extensions();
let mashina = Machine::with_vapash_extensions(
spec.params().clone(),
Default::default(),
vapparams,
);
let mut header = Header::new();
header.set_number(15);
let res = mashina.verify_transaction_basic(&transaction, &header);
assert_eq!(res, Err(transaction::Error::InvalidSignature("invalid EC signature".into())));
}
#[test]
fn vapash_gas_limit_is_multiple_of_determinant() {
use vapory_types::U256;
let spec = vapcore_spec::new_homestead_test();
let vapparams = get_default_vapash_extensions();
let mashina = Machine::with_vapash_extensions(
spec.params().clone(),
Default::default(),
vapparams,
);
let mut parent = Header::new();
let mut header = Header::new();
header.set_number(1);
assert_eq!(TETSY_GAS_LIMIT_DETERMINANT, U256::from(37));
parent.set_gas_limit(U256::from(50_000));
mashina.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
assert_eq!(*header.gas_limit(), U256::from(50_024));
parent.set_gas_limit(U256::from(250_000));
mashina.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
assert_eq!(*header.gas_limit(), U256::from(249_787));
header.set_gas_used(U256::from(150_000));
parent.set_gas_limit(U256::from(150_000));
mashina.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(200_000));
assert_eq!(*header.gas_limit(), U256::from(150_035));
header.set_gas_used(U256::from(150_000));
parent.set_gas_limit(U256::from(150_000));
mashina.populate_from_parent(&mut header, &parent, U256::from(100_000), U256::from(150_002));
assert_eq!(*header.gas_limit(), U256::from(149_998));
header.set_gas_used(U256::from(150_000));
parent.set_gas_limit(U256::from(150_000));
mashina.populate_from_parent(&mut header, &parent, U256::from(150_000), U256::from(150_002));
assert_eq!(*header.gas_limit(), U256::from(150_002));
}
}