EIP2929 with journaling + Yolov3 (#79)

This commit is contained in:
adria0.eth
2020-11-04 19:11:05 +01:00
committed by GitHub
parent 50a4d5fa57
commit 6078eeaed7
29 changed files with 921 additions and 199 deletions

View File

@@ -0,0 +1,260 @@
use ethereum_types::{Address, H256};
use std::{
borrow::Borrow,
collections::HashMap,
hash::{Hash, Hasher},
};
use std::{cell::RefCell, rc::Rc};
// Implementation of a hasheable borrowed pair
trait KeyPair<A, B> {
fn a(&self) -> &A;
fn b(&self) -> &B;
}
impl<'a, A, B> Borrow<dyn KeyPair<A, B> + 'a> for (A, B)
where
A: Eq + Hash + 'a,
B: Eq + Hash + 'a,
{
fn borrow(&self) -> &(dyn KeyPair<A, B> + 'a) {
self
}
}
impl<A: Hash, B: Hash> Hash for (dyn KeyPair<A, B> + '_) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.a().hash(state);
self.b().hash(state);
}
}
impl<A: Eq, B: Eq> PartialEq for (dyn KeyPair<A, B> + '_) {
fn eq(&self, other: &Self) -> bool {
self.a() == other.a() && self.b() == other.b()
}
}
impl<A: Eq, B: Eq> Eq for (dyn KeyPair<A, B> + '_) {}
impl<A, B> KeyPair<A, B> for (A, B) {
fn a(&self) -> &A {
&self.0
}
fn b(&self) -> &B {
&self.1
}
}
impl<A, B> KeyPair<A, B> for (&A, &B) {
fn a(&self) -> &A {
self.0
}
fn b(&self) -> &B {
self.1
}
}
#[derive(Debug)]
struct Journal {
enabled: bool,
last_id: usize,
addresses: HashMap<Address, usize>,
storage_keys: HashMap<(Address, H256), usize>,
}
#[derive(Debug)]
pub struct AccessList {
id: usize,
journal: Rc<RefCell<Journal>>,
}
impl Clone for AccessList {
fn clone(&self) -> Self {
let mut journal = self.journal.as_ref().borrow_mut();
let id = journal.last_id + 1;
journal.last_id = id;
Self {
id: id,
journal: self.journal.clone(),
}
}
}
impl Default for AccessList {
fn default() -> Self {
AccessList::new(false)
}
}
impl std::fmt::Display for AccessList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let journal = self.journal.as_ref().borrow();
for (addr, id) in journal.addresses.iter() {
write!(f, "| ADDR {} -> {}\n", addr, id)?;
}
for ((addr, slot), id) in journal.storage_keys.iter() {
write!(f, "| SLOT {}:{} -> {}\n", addr, slot, id)?;
}
Ok(())
}
}
impl AccessList {
/// Returns if the list is enabled
pub fn new(enabled: bool) -> Self {
let journal = Journal {
enabled,
last_id: 0,
addresses: HashMap::new(),
storage_keys: HashMap::new(),
};
Self {
id: 0,
journal: Rc::new(RefCell::new(journal)),
}
}
/// Returns if the list is enabled
pub fn is_enabled(&self) -> bool {
let journal = self.journal.as_ref().borrow();
journal.enabled
}
/// Enable the access list control
pub fn enable(&mut self) {
let mut journal = self.journal.as_ref().borrow_mut();
journal.enabled = true;
}
/// Checks if contains an storage key
pub fn contains_storage_key(&self, address: &Address, key: &H256) -> bool {
let journal = self.journal.as_ref().borrow();
if journal.enabled {
journal
.storage_keys
.contains_key(&(address, key) as &dyn KeyPair<Address, H256>)
} else {
false
}
}
/// Inserts a storage key
pub fn insert_storage_key(&mut self, address: Address, key: H256) {
let mut journal = self.journal.as_ref().borrow_mut();
if journal.enabled
&& !journal
.storage_keys
.contains_key(&(address, key) as &dyn KeyPair<Address, H256>)
{
journal.storage_keys.insert((address, key), self.id);
}
}
/// Checks if contains an address
pub fn contains_address(&self, address: &Address) -> bool {
let journal = self.journal.as_ref().borrow();
if journal.enabled {
journal.addresses.contains_key(&address)
} else {
false
}
}
/// Inserts an address
pub fn insert_address(&mut self, address: Address) {
let mut journal = self.journal.as_ref().borrow_mut();
if journal.enabled && !journal.addresses.contains_key(&address) {
journal.addresses.insert(address, self.id);
}
}
/// Removes all changes in journal
pub fn rollback(&self) {
let mut journal = self.journal.as_ref().borrow_mut();
// `id < self.id` instead `id != self.if` is to take care about recursive calls
journal.addresses.retain(|_, id| *id < self.id);
journal.storage_keys.retain(|_, id| *id < self.id);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_accesslist_is_disabled() {
let access_list = AccessList::default();
assert_eq!(false, access_list.is_enabled());
}
#[test]
fn default_disabled_accesslist_does_nothing() {
let mut access_list = AccessList::default();
access_list.insert_address(Address::from(1));
access_list.insert_storage_key(Address::from(2), H256::from(3));
assert_eq!(false, access_list.contains_address(&Address::from(1)));
assert_eq!(
false,
access_list.contains_storage_key(&Address::from(2), &H256::from(3))
);
}
#[test]
fn default_enabled_accesslist_registers() {
let mut access_list = AccessList::default();
access_list.enable();
assert_eq!(true, access_list.is_enabled());
access_list.insert_address(Address::from(1));
access_list.insert_storage_key(Address::from(2), H256::from(3));
assert_eq!(true, access_list.contains_address(&Address::from(1)));
assert_eq!(
true,
access_list.contains_storage_key(&Address::from(2), &H256::from(3))
);
}
#[test]
fn cloned_accesslist_registers_in_parent() {
let mut access_list = AccessList::default();
access_list.enable();
assert_eq!(true, access_list.is_enabled());
access_list.insert_address(Address::from(1));
access_list.insert_storage_key(Address::from(2), H256::from(3));
let mut access_list_call = access_list.clone();
assert_eq!(true, access_list_call.contains_address(&Address::from(1)));
assert_eq!(
true,
access_list_call.contains_storage_key(&Address::from(2), &H256::from(3))
);
access_list.insert_address(Address::from(4));
assert_eq!(true, access_list_call.contains_address(&Address::from(4)));
assert_eq!(true, access_list.contains_address(&Address::from(4)));
}
#[test]
fn cloned_accesslist_rollbacks_in_parent() {
let mut access_list = AccessList::default();
access_list.enable();
assert_eq!(true, access_list.is_enabled());
access_list.insert_address(Address::from(1));
access_list.insert_storage_key(Address::from(2), H256::from(3));
let mut access_list_call = access_list.clone();
access_list_call.insert_address(Address::from(1));
access_list_call.insert_storage_key(Address::from(2), H256::from(3));
access_list_call.insert_address(Address::from(4));
let mut access_list_call_call = access_list.clone();
access_list_call_call.insert_address(Address::from(1));
access_list_call_call.insert_storage_key(Address::from(2), H256::from(3));
access_list_call_call.insert_address(Address::from(5));
access_list_call_call.insert_storage_key(Address::from(6), H256::from(7));
access_list_call.rollback();
assert_eq!(true, access_list.contains_address(&Address::from(1)));
assert_eq!(false, access_list.contains_address(&Address::from(4)));
assert_eq!(false, access_list.contains_address(&Address::from(5)));
assert_eq!(
true,
access_list.contains_storage_key(&Address::from(2), &H256::from(3))
);
assert_eq!(
false,
access_list.contains_storage_key(&Address::from(6), &H256::from(7))
);
}
}

View File

@@ -15,13 +15,13 @@
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
//! Evm input params.
use super::access_list::AccessList;
use bytes::Bytes;
use call_type::CallType;
use ethereum_types::{Address, H256, U256};
use ethjson;
use hash::{keccak, KECCAK_EMPTY};
use call_type::CallType;
use std::sync::Arc;
/// Transaction value
@@ -90,6 +90,8 @@ pub struct ActionParams {
pub call_type: CallType,
/// Param types encoding
pub params_type: ParamsType,
/// Current access list
pub access_list: AccessList,
}
impl Default for ActionParams {
@@ -108,6 +110,7 @@ impl Default for ActionParams {
data: None,
call_type: CallType::None,
params_type: ParamsType::Separate,
access_list: AccessList::default(),
}
}
}
@@ -131,6 +134,7 @@ impl From<ethjson::vm::Transaction> for ActionParams {
false => CallType::Call,
}, // TODO @debris is this correct?
params_type: ParamsType::Separate,
access_list: AccessList::default(),
}
}
}

View File

@@ -101,6 +101,9 @@ pub trait Ext {
trap: bool,
) -> ::std::result::Result<ContractCreateResult, TrapKind>;
/// Returns the address that will be created in the create call
fn calc_address(&self, code: &[u8], address: CreateContractAddress) -> Option<Address>;
/// Message call.
///
/// Returns Err, if we run out of gas.
@@ -184,4 +187,19 @@ pub trait Ext {
/// Check if running in static context.
fn is_static(&self) -> bool;
/// Returns if the list is enabled
fn al_is_enabled(&self) -> bool;
/// Checks if contains an storage key
fn al_contains_storage_key(&self, address: &Address, key: &H256) -> bool;
/// Inserts an storage key into the list
fn al_insert_storage_key(&mut self, address: Address, key: H256);
/// Checks if contains an address
fn al_contains_address(&self, address: &Address) -> bool;
/// Inserts an address into the list
fn al_insert_address(&mut self, address: Address);
}

View File

@@ -23,16 +23,18 @@ extern crate parity_bytes as bytes;
extern crate patricia_trie_ethereum as ethtrie;
extern crate rlp;
pub mod access_list;
mod action_params;
mod call_type;
mod env_info;
mod error;
mod ext;
mod return_data;
mod schedule;
pub mod schedule;
pub mod tests;
pub use access_list::AccessList;
pub use action_params::{ActionParams, ActionValue, ParamsType};
pub use call_type::CallType;
pub use env_info::{EnvInfo, LastHashes};
@@ -42,7 +44,7 @@ pub use return_data::{GasLeft, ReturnData};
pub use schedule::{CleanDustMode, Schedule, WasmCosts};
/// Virtual Machine interface
pub trait Exec: Send {
pub trait Exec {
/// This function should be used to execute transaction.
/// It returns either an error, a known amount of gas left, or parameters to be used
/// to compute the final gas left.
@@ -50,13 +52,13 @@ pub trait Exec: Send {
}
/// Resume call interface
pub trait ResumeCall: Send {
pub trait ResumeCall {
/// Resume an execution for call, returns back the Vm interface.
fn resume_call(self: Box<Self>, result: MessageCallResult) -> Box<dyn Exec>;
}
/// Resume create interface
pub trait ResumeCreate: Send {
pub trait ResumeCreate {
/// Resume an execution from create, returns back the Vm interface.
fn resume_create(self: Box<Self>, result: ContractCreateResult) -> Box<dyn Exec>;
}

View File

@@ -16,6 +16,15 @@
//! Cost schedule and other parameterisations for the EVM.
// Gas per non accessed address when sload
pub const EIP2929_COLD_SLOAD_COST: usize = 2100;
// Gas per non accessed address accessing account from other opcodes defined in EIP2929
pub const EIP2929_COLD_ACCOUNT_ACCESS_COST: usize = 2600;
// Gas per already accessed address
pub const EIP2929_WARM_STORAGE_READ_COST: usize = 100;
// Gas per sstore reset
pub const EIP2929_SSTORE_RESET_GAS: usize = 5000 - EIP2929_COLD_SLOAD_COST;
/// Definition of the cost schedule and other parameterisations for the EVM.
#[derive(Debug)]
pub struct Schedule {
@@ -63,6 +72,12 @@ pub struct Schedule {
pub create_gas: usize,
/// Gas price for `*CALL*` opcodes
pub call_gas: usize,
/// EIP-2929 COLD_SLOAD_COST
pub cold_sload_cost: usize,
/// EIP-2929 COLD_ACCOUNT_ACCESS_COST
pub cold_account_access_cost: usize,
/// EIP-2929 WARM_STORAGE_READ_COST
pub warm_storage_read_cost: usize,
/// Stipend for transfer for `CALL|CALLCODE` opcode when `value>0`
pub call_stipend: usize,
/// Additional gas required for value transfer (`CALL|CALLCODE`)
@@ -132,6 +147,8 @@ pub struct Schedule {
pub keep_unsigned_nonce: bool,
/// Wasm extra schedule settings, if wasm activated
pub wasm: Option<WasmCosts>,
/// Enable EIP-2929 rules
pub eip2929: bool,
}
/// Wasm cost table
@@ -245,6 +262,9 @@ impl Schedule {
log_topic_gas: 375,
create_gas: 32000,
call_gas: 700,
cold_account_access_cost: 0,
cold_sload_cost: 0,
warm_storage_read_cost: 0,
call_stipend: 2300,
call_value_transfer_gas: 9000,
call_new_account_gas: 25000,
@@ -274,6 +294,7 @@ impl Schedule {
eip1706: false,
keep_unsigned_nonce: false,
wasm: None,
eip2929: false,
}
}
@@ -290,7 +311,8 @@ impl Schedule {
/// Schedule for the Constantinople fork of the Ethereum main net.
pub fn new_constantinople() -> Schedule {
let mut schedule = Self::new_byzantium();
schedule.have_bitwise_shifting = true;
schedule.have_bitwise_shifting = true; // EIP 145
schedule.have_extcodehash = true; // EIP 1052
schedule
}
@@ -310,6 +332,30 @@ impl Schedule {
pub fn new_berlin() -> Schedule {
let mut schedule = Self::new_istanbul();
schedule.have_subs = true; // EIP 2315
schedule
}
/// Schedule for the Yolov3 testnet of the Ethereum main net.
pub fn new_yolo3() -> Schedule {
let mut schedule = Self::new_istanbul();
schedule.have_subs = true; // EIP 2315
schedule.eip1283 = true;
schedule.eip2929 = true;
schedule.cold_sload_cost = EIP2929_COLD_SLOAD_COST;
schedule.cold_account_access_cost = EIP2929_COLD_ACCOUNT_ACCESS_COST;
schedule.warm_storage_read_cost = EIP2929_WARM_STORAGE_READ_COST;
schedule.sload_gas = EIP2929_WARM_STORAGE_READ_COST;
schedule.call_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST;
schedule.balance_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST;
schedule.extcodecopy_base_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST;
schedule.extcodehash_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST;
schedule.extcodesize_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST;
schedule.sstore_reset_gas = EIP2929_SSTORE_RESET_GAS;
schedule
}
@@ -342,6 +388,9 @@ impl Schedule {
log_topic_gas: 375,
create_gas: 32000,
call_gas: 40,
cold_account_access_cost: 0,
cold_sload_cost: 0,
warm_storage_read_cost: 0,
call_stipend: 2300,
call_value_transfer_gas: 9000,
call_new_account_gas: 25000,
@@ -371,6 +420,7 @@ impl Schedule {
eip1706: false,
keep_unsigned_nonce: false,
wasm: None,
eip2929: false,
}
}

View File

@@ -19,6 +19,7 @@ use std::{
sync::Arc,
};
use crate::access_list::AccessList;
use bytes::Bytes;
use error::TrapKind;
use ethereum_types::{Address, H256, U256};
@@ -75,6 +76,7 @@ pub struct FakeExt {
pub balances: HashMap<Address, U256>,
pub tracing: bool,
pub is_static: bool,
pub access_list: AccessList,
chain_id: u64,
}
@@ -122,6 +124,19 @@ impl FakeExt {
ext
}
/// New fake externalities with YoloV2 schedule rules
pub fn new_yolo3(from: Address, to: Address, builtins: &[Address]) -> Self {
let mut ext = FakeExt::default();
ext.schedule = Schedule::new_yolo3();
ext.access_list.enable();
ext.access_list.insert_address(from);
ext.access_list.insert_address(to);
for builtin in builtins {
ext.access_list.insert_address(*builtin);
}
ext
}
/// Alter fake externalities to allow wasm
pub fn with_wasm(mut self) -> Self {
self.schedule.wasm = Some(Default::default());
@@ -162,7 +177,7 @@ impl Ext for FakeExt {
}
fn balance(&self, address: &Address) -> Result<U256> {
Ok(self.balances[address])
Ok(self.balances.get(address).cloned().unwrap_or(U256::zero()))
}
fn blockhash(&mut self, number: &U256) -> H256 {
@@ -191,6 +206,10 @@ impl Ext for FakeExt {
Ok(ContractCreateResult::Failed)
}
fn calc_address(&self, _code: &[u8], _address: CreateContractAddress) -> Option<Address> {
None
}
fn call(
&mut self,
gas: &U256,
@@ -276,4 +295,24 @@ impl Ext for FakeExt {
fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _gas: U256) -> bool {
self.tracing
}
fn al_is_enabled(&self) -> bool {
self.access_list.is_enabled()
}
fn al_contains_storage_key(&self, address: &Address, key: &H256) -> bool {
self.access_list.contains_storage_key(address, key)
}
fn al_insert_storage_key(&mut self, address: Address, key: H256) {
self.access_list.insert_storage_key(address, key)
}
fn al_contains_address(&self, address: &Address) -> bool {
self.access_list.contains_address(address)
}
fn al_insert_address(&mut self, address: Address) {
self.access_list.insert_address(address)
}
}